Va chạm bóng với bóng - Phát hiện và xử lý


266

Với sự giúp đỡ của cộng đồng Stack Overflow, tôi đã viết một trình giả lập vật lý khá cơ bản - nhưng thú vị.

văn bản thay thế

Bạn nhấp và kéo chuột để khởi động một quả bóng. Nó sẽ nảy xung quanh và cuối cùng dừng lại trên "sàn".

Tính năng lớn tiếp theo của tôi, tôi muốn thêm vào là va chạm bóng. Chuyển động của quả bóng được chia thành vectơ tốc độ rìu và y. Tôi có lực hấp dẫn (giảm nhỏ vectơ y mỗi bước), tôi có ma sát (giảm nhỏ cả hai vectơ mỗi va chạm với tường). Các quả bóng trung thực di chuyển xung quanh một cách thực tế đáng ngạc nhiên.

Tôi đoán câu hỏi của tôi có hai phần:

  1. Phương pháp tốt nhất để phát hiện bóng để va chạm bóng là gì?
    Tôi có chỉ có một vòng lặp O (n ^ 2) lặp lại trên mỗi quả bóng và kiểm tra từng quả bóng khác để xem liệu bán kính của nó có trùng nhau không?
  2. Những phương trình nào tôi sử dụng để xử lý bóng để va chạm bóng? Vật lý 101
    Làm thế nào để nó ảnh hưởng đến hai vectơ tốc độ x / y? Hướng kết quả của hai quả bóng đi vào là gì? Làm thế nào để tôi áp dụng điều này cho mỗi quả bóng?

văn bản thay thế

Xử lý phát hiện va chạm của các "bức tường" và thay đổi vectơ kết quả là dễ dàng nhưng tôi thấy nhiều biến chứng hơn với va chạm bóng. Với các bức tường, tôi chỉ cần lấy âm của vectơ x hoặc y thích hợp và tắt nó đi theo hướng chính xác. Với những quả bóng tôi không nghĩ nó là như vậy.

Một số giải thích nhanh: hiện tại tôi vẫn ổn với một va chạm hoàn toàn đàn hồi, tất cả các quả bóng của tôi đều có cùng khối lượng ngay bây giờ, nhưng tôi có thể thay đổi điều đó trong tương lai.


Chỉnh sửa: Tài nguyên tôi thấy hữu ích

Vật lý bóng 2d với vectơ: Va chạm 2 chiều không có lượng
giác.pdf Ví dụ phát hiện va chạm bóng 2d: Thêm phát hiện va chạm


Sự thành công!

Tôi có phát hiện va chạm bóng và phản ứng làm việc tuyệt vời!

Mã liên quan:

Phát hiện va chạm:

for (int i = 0; i < ballCount; i++)  
{  
    for (int j = i + 1; j < ballCount; j++)  
    {  
        if (balls[i].colliding(balls[j]))  
        {
            balls[i].resolveCollision(balls[j]);
        }
    }
}

Điều này sẽ kiểm tra va chạm giữa mỗi quả bóng nhưng bỏ qua kiểm tra dư thừa (nếu bạn phải kiểm tra xem bóng 1 có va chạm với bóng 2 không thì bạn không cần kiểm tra xem bóng 2 có va chạm với bóng không 1. Ngoài ra, nó bỏ qua việc kiểm tra va chạm với chính nó ).

Sau đó, trong lớp bóng của tôi, tôi có các phương thức va chạm () và notifyCollision ():

public boolean colliding(Ball ball)
{
    float xd = position.getX() - ball.position.getX();
    float yd = position.getY() - ball.position.getY();

    float sumRadius = getRadius() + ball.getRadius();
    float sqrRadius = sumRadius * sumRadius;

    float distSqr = (xd * xd) + (yd * yd);

    if (distSqr <= sqrRadius)
    {
        return true;
    }

    return false;
}

public void resolveCollision(Ball ball)
{
    // get the mtd
    Vector2d delta = (position.subtract(ball.position));
    float d = delta.getLength();
    // minimum translation distance to push balls apart after intersecting
    Vector2d mtd = delta.multiply(((getRadius() + ball.getRadius())-d)/d); 


    // resolve intersection --
    // inverse mass quantities
    float im1 = 1 / getMass(); 
    float im2 = 1 / ball.getMass();

    // push-pull them apart based off their mass
    position = position.add(mtd.multiply(im1 / (im1 + im2)));
    ball.position = ball.position.subtract(mtd.multiply(im2 / (im1 + im2)));

    // impact speed
    Vector2d v = (this.velocity.subtract(ball.velocity));
    float vn = v.dot(mtd.normalize());

    // sphere intersecting but moving away from each other already
    if (vn > 0.0f) return;

    // collision impulse
    float i = (-(1.0f + Constants.restitution) * vn) / (im1 + im2);
    Vector2d impulse = mtd.normalize().multiply(i);

    // change in momentum
    this.velocity = this.velocity.add(impulse.multiply(im1));
    ball.velocity = ball.velocity.subtract(impulse.multiply(im2));

}

Mã nguồn: Hoàn thành nguồn cho bóng để va chạm bóng.

Nếu ai có một số gợi ý về cách cải thiện trình mô phỏng vật lý cơ bản này, hãy cho tôi biết! Một điều tôi chưa thêm là động lượng góc để các quả bóng sẽ lăn thực tế hơn. Bất cứ một đề nghị nào khác? Để lại một bình luận!


16
Tôi không nghĩ thuật toán này đủ tốt bởi vì nếu các quả bóng của bạn di chuyển quá nhanh (ví dụ: nhanh hơn bán kính 2 * trên mỗi khung hình, một quả bóng có thể đi qua một quả bóng khác mà không có bất kỳ va chạm nào.
Benji Mizrahi

@Simulcal bạn có thể tải lên mã nguồn của mình một lần nữa không (tất cả các liên kết tệp filerpper.com dường như bị hỏng). Ngoài ra, bạn có thể đưa lên tệp pdf mà bạn nhận được từ [geocities.com/vobarian/2dcollutions/2dcollutions.pdf] vì các địa lý đã ngoại tuyến gần đây
bguiz

1
Đây là liên kết đến phiên bản BallBounce cuối cùng mà tôi đã làm việc trên: dl.dropbox.com/u/638285/ballbounce.rar
mmcdole

@ Để tất cả những người đã đóng góp: Bạn có thể vui lòng làm sáng tỏ để chuyển đổi công cụ này thành 3D. Làm thế nào công cụ tuyệt vời này cũng có thể hoạt động trong Java3D.
khoảng trống tĩnh chính

2
Dòng Vector2d impulse = mtd.multiply(i);nên là i * vectơ mtd chuẩn hóa. Một cái gì đó như:Vector2d impulse = mtd.normalize().multiply(i);
klenwell

Câu trả lời:


117

Để phát hiện xem hai quả bóng có va chạm hay không, chỉ cần kiểm tra xem khoảng cách giữa tâm của chúng có nhỏ hơn hai lần bán kính hay không. Để thực hiện một va chạm hoàn toàn đàn hồi giữa các quả bóng, bạn chỉ cần lo lắng về thành phần của vận tốc theo hướng va chạm. Thành phần khác (tiếp tuyến với va chạm) sẽ giữ nguyên cho cả hai quả bóng. Bạn có thể có được các thành phần va chạm bằng cách tạo một vectơ đơn vị chỉ theo hướng từ quả bóng này sang quả bóng khác, sau đó lấy sản phẩm chấm với vectơ vận tốc của quả bóng. Sau đó, bạn có thể cắm các thành phần này vào phương trình va chạm hoàn toàn đàn hồi 1D.

Wikipedia có một bản tóm tắt khá tốt về toàn bộ quá trình . Đối với các quả bóng có khối lượng bất kỳ, vận tốc mới có thể được tính bằng các phương trình (trong đó v1 và v2 là vận tốc sau va chạm và u1, u2 là từ trước):

v_ {1} = \ frac {u_ {1} (m_ {1} -m_ {2}) + 2m_ {2} u_ {2}} {m_ {1} + m_ {2}}

v_ {2} = \ frac {u_ {2} (m_ {2} -m_ {1}) + 2m_ {1} u_ {1}} {m_ {1} + m_ {2}}

Nếu các quả bóng có cùng khối lượng thì vận tốc được chuyển đổi đơn giản. Đây là một số mã tôi đã viết mà làm một cái gì đó tương tự:

void Simulation::collide(Storage::Iterator a, Storage::Iterator b)
{
    // Check whether there actually was a collision
    if (a == b)
        return;

    Vector collision = a.position() - b.position();
    double distance = collision.length();
    if (distance == 0.0) {              // hack to avoid div by zero
        collision = Vector(1.0, 0.0);
        distance = 1.0;
    }
    if (distance > 1.0)
        return;

    // Get the components of the velocity vectors which are parallel to the collision.
    // The perpendicular component remains the same for both fish
    collision = collision / distance;
    double aci = a.velocity().dot(collision);
    double bci = b.velocity().dot(collision);

    // Solve for the new velocities using the 1-dimensional elastic collision equations.
    // Turns out it's really simple when the masses are the same.
    double acf = bci;
    double bcf = aci;

    // Replace the collision velocity components with the new ones
    a.velocity() += (acf - aci) * collision;
    b.velocity() += (bcf - bci) * collision;
}

Về hiệu quả, Ryan Fox đã đúng, bạn nên xem xét việc chia khu vực thành các phần, sau đó thực hiện phát hiện va chạm trong mỗi phần. Hãy nhớ rằng các quả bóng có thể va chạm với các quả bóng khác trên ranh giới của một phần, vì vậy điều này có thể làm cho mã của bạn phức tạp hơn nhiều. Hiệu quả có lẽ sẽ không thành vấn đề cho đến khi bạn có vài trăm quả bóng. Đối với các điểm thưởng, bạn có thể chạy từng phần trên một lõi khác nhau hoặc phân chia việc xử lý các va chạm trong mỗi phần.


2
Hãy nói rằng khối lượng của hai quả bóng không bằng nhau. Làm thế nào mà hiệu ứng thay đổi vector giữa các quả bóng?
mmcdole

3
Đã được một thời gian kể từ lớp 12, nhưng tôi nghĩ họ có được tỷ lệ động lượng tương ứng với tỷ lệ của khối lượng.
Ryan Fox

6
@Jay, chỉ để chỉ ra .. rằng một hình ảnh phương trình bạn đã thêm là cho va chạm 1 chiều, không phải 2 chiều.
mmcdole

@simucal. không đúng ... u và v là các vectơ trong phương trình đó. Đó là, họ có các thành phần x, y (và z).
Andrew Rollings

2
@Simucal, bạn nói đúng, họ dành cho trường hợp một chiều. Để biết thêm kích thước, chỉ cần sử dụng các thành phần của vận tốc phù hợp với va chạm (aci, bci trong mã). Các thành phần khác trực giao với va chạm và sẽ không thay đổi, vì vậy bạn không cần phải lo lắng về chúng.
Jay Conrod

48

Vâng, nhiều năm trước tôi đã thực hiện chương trình như bạn trình bày ở đây.
Có một vấn đề tiềm ẩn (hoặc nhiều vấn đề, tùy thuộc vào quan điểm):

  • Nếu tốc độ của quả bóng quá cao, bạn có thể bỏ lỡ va chạm.

Và cũng như vậy, gần như trong 100% trường hợp tốc độ mới của bạn sẽ sai. Vâng, không phải tốc độ , nhưng vị trí . Bạn phải tính toán tốc độ mới chính xác ở đúng nơi. Nếu không, bạn chỉ cần chuyển bóng trên một số lượng "lỗi" nhỏ, có sẵn từ bước rời rạc trước đó.

Giải pháp rất rõ ràng: bạn phải phân chia dấu thời gian sao cho đầu tiên bạn chuyển sang vị trí chính xác, sau đó va chạm, sau đó thay đổi trong phần còn lại của thời gian bạn có.


Nếu thay đổi vị trí trên timeframelength*speed/2, thì vị trí sẽ được cố định thống kê.
Nakilon

@Nakilon: không, nó chỉ giúp trong một số trường hợp, nhưng nói chung có thể bỏ lỡ vụ va chạm. Và xác suất bỏ lỡ va chạm tăng theo kích thước của bước sóng thời gian. Nhân tiện, có vẻ như Aleph đã trình diễn giải pháp chính xác (tôi chỉ lướt qua nó thôi).
avp

1
@avp, tôi không về Nếu tốc độ của quả bóng quá cao, bạn có thể bỏ lỡ va chạm. , nhưng về vị trí mới của bạn sẽ sai . Do va chạm được phát hiện muộn hơn một chút, so với khi chúng thực sự va chạm, nếu trừ đi timeframelength*speed/2từ vị trí đó, độ chính xác sẽ tăng gấp đôi.
Nakilon

20

Bạn nên sử dụng phân vùng không gian để giải quyết vấn đề này.

Đọc lên Phân vùng không gian nhị phânQuadtrees


4
Thay vì phân vùng không gian, thuật toán quét và cắt tỉa có hoạt động tốt hơn không? các quả bóng đang di chuyển nhanh, do đó, bất kỳ phân vùng sẽ phải được cập nhật thường xuyên, phát sinh chi phí. Quét và cắt tỉa có thể tìm thấy tất cả các cặp va chạm trong O (n log n), mà không có bất kỳ cấu trúc dữ liệu nhất thời. Đây là một hướng dẫn tốt cho những điều cơ bản
HugoRune

13

Như một lời giải thích cho đề xuất của Ryan Fox để chia màn hình thành các khu vực và chỉ kiểm tra va chạm trong các khu vực ...

ví dụ: chia khu vực chơi thành một lưới các ô vuông (sẽ tùy ý nói là có độ dài 1 đơn vị mỗi bên) và kiểm tra các va chạm trong mỗi ô vuông.

Đó hoàn toàn là giải pháp chính xác. Vấn đề duy nhất với nó (như một poster khác đã chỉ ra) là sự va chạm xuyên qua các ranh giới là một vấn đề.

Giải pháp cho vấn đề này là phủ một lưới thứ hai ở mức bù dọc và ngang 0,5 đơn vị cho lưới thứ nhất.

Sau đó, bất kỳ va chạm nào sẽ vượt qua các ranh giới trong lưới đầu tiên (và do đó không được phát hiện) sẽ nằm trong các ô vuông trong lưới thứ hai. Miễn là bạn theo dõi các va chạm mà bạn đã xử lý (vì có khả năng xảy ra sự chồng chéo), bạn không phải lo lắng về việc xử lý các trường hợp cạnh. Tất cả các va chạm sẽ nằm trong một ô vuông trên một trong các lưới.


+1 cho một giải pháp chính xác hơn và để chống lại kẻ hạ bệ lái xe hèn nhát
Steven A. Lowe

1
đó là một ý kiến ​​hay. Tôi đã làm điều này một lần và tôi đã kiểm tra ô hiện tại và tất cả các ô lân cận, nhưng phương pháp của bạn hiệu quả hơn. Một cách khác tôi chỉ nghĩ là kiểm tra ô hiện tại, rồi kiểm tra xem liệu nó có giao với ranh giới của các ô hiện tại hay không, và nếu vậy, hãy kiểm tra các đối tượng trong THAT lân cận.
LoveMeSomeCode

10

Một cách tốt để giảm số lần kiểm tra va chạm là chia màn hình thành các phần khác nhau. Sau đó, bạn chỉ so sánh từng quả bóng với các quả bóng trong cùng một phần.


5
Khắc phục: bạn cần kiểm tra các va chạm với các phần tương tự VÀ liền kề
rint

7

Một điều tôi thấy ở đây để tối ưu hóa.

Mặc dù tôi đồng ý rằng các quả bóng đánh khi khoảng cách là tổng của bán kính của chúng, người ta không bao giờ nên thực sự tính khoảng cách này! Thay vào đó, tính toán nó vuông và làm việc theo cách đó. Không có lý do cho hoạt động căn bậc hai đắt tiền đó.

Ngoài ra, một khi bạn đã tìm thấy một vụ va chạm, bạn phải tiếp tục đánh giá các vụ va chạm cho đến khi không còn tồn tại nữa. Vấn đề là cái đầu tiên có thể khiến những người khác phải giải quyết trước khi bạn có được một bức tranh chính xác. Hãy xem xét những gì xảy ra nếu quả bóng chạm một quả bóng ở rìa? Quả bóng thứ hai chạm vào mép và ngay lập tức bật lại thành quả bóng đầu tiên. Nếu bạn đập vào một đống bóng ở góc, bạn có thể có một vài va chạm phải được giải quyết trước khi bạn có thể lặp lại chu kỳ tiếp theo.

Đối với O (n ^ 2), tất cả những gì bạn có thể làm là giảm thiểu chi phí từ chối những người bỏ lỡ:

1) Một quả bóng không di chuyển không thể đánh bất cứ thứ gì. Nếu có một số lượng bóng hợp lý nằm xung quanh trên sàn, điều này có thể tiết kiệm rất nhiều bài kiểm tra. (Lưu ý rằng bạn vẫn phải kiểm tra nếu có thứ gì đó chạm vào quả bóng đứng yên.)

2) Điều gì đó có thể đáng làm: Chia màn hình thành một số khu vực nhưng các đường nên mờ - các quả bóng ở rìa của một khu vực được liệt kê là nằm trong tất cả các khu vực có thể (có thể là 4). Tôi sẽ sử dụng lưới 4 x 4, lưu trữ các vùng dưới dạng bit. Nếu một AND của các khu vực của hai quả bóng trả về 0, kết thúc thử nghiệm.

3) Như tôi đã đề cập, đừng làm căn bậc hai.


Cảm ơn bạn đã thông tin về mẹo căn bậc hai. Không biết về bản chất đắt tiền của nó khi so sánh với hình vuông.
mmcdole

Một tối ưu hóa khác là tìm những quả bóng không ở gần bất kỳ quả bóng nào khác. Điều này sẽ hoạt động đáng tin cậy chỉ khi vận tốc của các quả bóng bị hạn chế.
Brad Gilbert

1
Tôi không đồng ý tìm kiếm những quả bóng bị cô lập. Điều đó cũng tốn kém như phát hiện vụ va chạm. Để cải thiện những thứ bạn cần một cái gì đó ít hơn O (n) cho quả bóng đang được đề cập.
Loren Pechtel

7

Tôi tìm thấy một trang tuyệt vời với thông tin về phát hiện va chạm và phản ứng trong 2D.

http://www.metanetsoftware.com/technique.html

Họ cố gắng giải thích nó được thực hiện như thế nào từ quan điểm học thuật. Họ bắt đầu với việc phát hiện va chạm giữa các đối tượng đơn giản và chuyển sang phản ứng va chạm và làm thế nào để mở rộng nó.

Chỉnh sửa: Liên kết cập nhật


3

Bạn có hai cách dễ dàng để làm điều này. Jay đã bao quát cách kiểm tra chính xác từ trung tâm của quả bóng.

Cách dễ dàng hơn là sử dụng hộp giới hạn hình chữ nhật, đặt kích thước hộp của bạn bằng 80% kích thước của quả bóng và bạn sẽ mô phỏng va chạm khá tốt.

Thêm một phương thức cho lớp bóng của bạn:

public Rectangle getBoundingRect()
{
   int ballHeight = (int)Ball.Height * 0.80f;
   int ballWidth = (int)Ball.Width * 0.80f;
   int x = Ball.X - ballWidth / 2;
   int y = Ball.Y - ballHeight / 2;

   return new Rectangle(x,y,ballHeight,ballWidth);
}

Sau đó, trong vòng lặp của bạn:

// Checks every ball against every other ball. 
// For best results, split it into quadrants like Ryan suggested. 
// I didn't do that for simplicity here.
for (int i = 0; i < balls.count; i++)
{
    Rectangle r1 = balls[i].getBoundingRect();

    for (int k = 0; k < balls.count; k++)
    {

        if (balls[i] != balls[k])
        {
            Rectangle r2 = balls[k].getBoundingRect();

            if (r1.Intersects(r2))
            {
                 // balls[i] collided with balls[k]
            }
        }
    }
}

1
Điều này sẽ làm cho các quả bóng đi vào nhau 20% khi va chạm ngang và dọc. Cũng có thể sử dụng hộp giới hạn tròn, vì sự khác biệt hiệu quả không đáng kể. Ngoài ra, (x-width)/2nên được x-width/2.
Markus Jarderot

Cuộc gọi tốt về lỗi đánh máy ưu tiên. Bạn sẽ thấy rằng hầu hết các trò chơi 2d sử dụng các hộp giới hạn hình chữ nhật trên các hình dạng không phải hình chữ nhật bởi vì nó nhanh và người dùng gần như không bao giờ nhận thấy.
FlySwat

Bạn có thể làm hộp giới hạn hình chữ nhật, sau đó nếu nó có một lần nhấn hãy kiểm tra hộp giới hạn hình tròn.
Brad Gilbert

1
@Jonathan Holland, vòng lặp bên trong của bạn nên dành cho (int k = i + 1; ...) Điều này sẽ loại bỏ tất cả các kiểm tra dư thừa. (tức là kiểm tra sự va chạm của bản thân và kiểm tra va chạm ball1 với ball2 rồi ball2 với ball1).
mmcdole

4
Trên thực tế, hộp giới hạn hình vuông có khả năng kém hiệu năng hơn so với hộp giới hạn hình tròn (giả sử bạn đã tối ưu hóa căn bậc hai đi)
Ponkadoodle

3

Tôi thấy nó được gợi ý ở đây và ở đó, nhưng trước tiên bạn cũng có thể thực hiện phép tính nhanh hơn, như so sánh các hộp giới hạn để chồng lấp và THEN thực hiện chồng lấp dựa trên bán kính nếu lần kiểm tra đầu tiên đó vượt qua.

Phép toán cộng / chênh lệch nhanh hơn nhiều đối với hộp giới hạn so với tất cả các trig cho bán kính và hầu hết các lần kiểm tra hộp giới hạn sẽ loại bỏ khả năng va chạm. Nhưng nếu sau đó bạn kiểm tra lại bằng trig, bạn sẽ nhận được kết quả chính xác mà bạn đang tìm kiếm.

Vâng, đó là hai bài kiểm tra, nhưng nó sẽ nhanh hơn về tổng thể.


6
Bạn không cần trig. bool is_overlapping(int x1, int y1, int r1, int x2, int y2, int r2) { return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)<(r1+r2)*(r1+r2); }
Ponkadoodle


2

Tôi đã triển khai mã này bằng JavaScript bằng cách sử dụng phần tử HTML Canvas và nó đã tạo ra các mô phỏng tuyệt vời với tốc độ 60 khung hình mỗi giây. Tôi bắt đầu mô phỏng với một bộ sưu tập hàng tá quả bóng ở các vị trí và vận tốc ngẫu nhiên. Tôi thấy rằng ở tốc độ cao hơn, một vụ va chạm giữa liếc một quả bóng nhỏ và một lớn hơn nhiều gây ra bóng nhỏ xuất hiện để STICK đến mép của bóng lớn hơn, và tăng lên khoảng 90 độ xung quanh bóng lớn hơn trước khi tách. (Tôi tự hỏi nếu có ai khác quan sát hành vi này.)

Một số ghi lại các tính toán cho thấy Khoảng cách dịch tối thiểu trong các trường hợp này không đủ lớn để ngăn các quả bóng giống nhau va chạm trong bước tiếp theo. Tôi đã thực hiện một số thử nghiệm và thấy rằng tôi có thể giải quyết vấn đề này bằng cách nhân rộng MTD dựa trên vận tốc tương đối:

dot_velocity = ball_1.velocity.dot(ball_2.velocity);
mtd_factor = 1. + 0.5 * Math.abs(dot_velocity * Math.sin(collision_angle));
mtd.multplyScalar(mtd_factor);

Tôi đã xác minh rằng trước và sau khi sửa lỗi này, tổng động năng được bảo toàn cho mỗi va chạm. Giá trị 0,5 trong mtd_factor là giá trị xấp xỉ được tìm thấy để luôn khiến các quả bóng tách ra sau một vụ va chạm.

Mặc dù bản sửa lỗi này đưa ra một lỗi nhỏ trong vật lý chính xác của hệ thống, nhưng sự đánh đổi là bây giờ các quả bóng rất nhanh có thể được mô phỏng trong trình duyệt mà không làm giảm kích thước bước thời gian.


1
sin (..) không phải là một chức năng rẻ tiền
PaulHK
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.