STDIO
Tìm kiếm gần đây
    • Nội dung
    • QR Code
    • 0
    • 0
    • Sao chép

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

    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.
    23/01/2015
    30/09/2020
    8 phút đọc
    Làm Sao Để Chia Tách, Cắt Một Đối Tượng với Box2D?

    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

    0 Bình luận
    Lập Trình Game

    Lập Trình Game

    Kiến thức, kỹ thuật, kinh nghiệm lập trình game.

    Đề xuất

    SOLID trong Lập Trình Hướng Đối Tượng
    SOLID trong lập trình hướng đối tượng có giá trị như thế nào trong việc ...
    Box2D - Phần 1: Giới Thiệu - Một Số Thuật Ngữ và Khái Niệm
    Giới thiệu engine xử lý vật lý Box2D, các khái niệm, cách thành phần ...

    Khám phá

    SmartHome - Từ Rời Rạc Hóa đến Tổng Thể Thông Minh
    Từ những thiết bị vốn được tạo ra tự động một cách rời rạc đến kết nối ...
    Tối Ưu Xử Lý Chuỗi với StringBuilder - Phần 1
    Bài viết giới thiệu và phân tích hiệu năng khi xử lí chuỗi với đối tượng ...
    03/02/2018
    Design Pattern: Adapter Pattern
    Bài viết đề cập đến một Design Pattern được sử dụng rất phổ biến trong ...
    Sơ Lược về Phong Cách Lập Trình
    Bài viết là một vài chia sẻ về cách hình thành phong cách lập trình để ...
    LEGO - Raspberry Pi: Sáng Tạo Ra Khỏi Cái Hộp
    Chia sẻ với các bạn dưới góc nhìn của một người sử dụng, nhà phát triển ...
    9 Tính Năng Quan Trọng Trong C++11
    C++11 là một phiên bản cải tiến và nâng cấp từ C++98 (hay các bạn vẫn ...
    13/08/2015
    MD5
    MD5 (MD5 Message-Digest Algorithm) là một thuật toán tóm tắt thông điệp, ...
    Giới Thiệu Ứng Dụng Của Làm Mờ Ảnh (Lọc Nhiễu) Trong Bài Toán Nhận Dạng
    Việc chọn phương pháp lọc nhiễu phù hợp sẽ giữ được các đặc trưng quan ...
    26/04/2016
    Khi bạn nhấn vào liên kết sản phẩm do STDIO đề xuất và mua hàng, STDIO có thể nhận được hoa hồng. Điều này hỗ trợ STDIO tạo thêm nhiều nội dung hữu ích. Tìm hiểu thêm.
    STDIO
    Trang chính
    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    developer@stdio.vn

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    ©STDIO, 2013 - 2020