Làm cách nào để kiểm tra hiệu quả nếu một điểm nằm trong hình chữ nhật được xoay?


11

Một phần vì mục đích tối ưu hóa, một phần cho mục đích học tập, tôi sẽ dám hỏi: Làm thế nào tôi có thể kiểm tra hiệu quả nhất một điểm 2D Pcó nằm trong hình chữ nhật xoay 2D hay không XYZW, sử dụng C # hoặc C ++?

Hiện tại, những gì tôi đang làm là sử dụng thuật toán "điểm trong tam giác" được tìm thấy trong cuốn sách Phát hiện va chạm thời gian thực và chạy nó hai lần (đối với hai hình tam giác tạo thành hình chữ nhật, giả sử XYZ và XZW):

bool PointInTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
 // Compute vectors        
 Vector2 v0 = C - A;
 Vector2 v1 = B - A;
 Vector2 v2 = P - A;

 // Compute dot products
 float dot00 = Vector2.Dot(v0, v0);
 float dot01 = Vector2.Dot(v0, v1);
 float dot02 = Vector2.Dot(v0, v2);
 float dot11 = Vector2.Dot(v1, v1);
 float dot12 = Vector2.Dot(v1, v2);

 // Compute barycentric coordinates
 float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
 float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
 float v = (dot00 * dot12 - dot01 * dot02) * invDenom;

 // Check if point is in triangle
 if(u >= 0 && v >= 0 && (u + v) < 1)
    { return true; } else { return false; }
}


bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
 if(PointInTriangle(X,Y,Z,P)) return true;
 if(PointInTriangle(X,Z,W,P)) return true;
 return false;
}

Tuy nhiên, tôi có cảm giác rằng có thể có một cách nhanh hơn và sạch hơn. Cụ thể, để giảm số lượng các phép toán.


Bạn có nhiều điểm, hay bạn có nhiều hình chữ nhật? Đó là câu hỏi đầu tiên bạn nên tự hỏi mình trước khi cố gắng tối ưu hóa một nhiệm vụ nhỏ như vậy.
sam hocevar

Điểm tốt. Tôi sẽ có một số điểm rất cao, nhưng thậm chí nhiều hình chữ nhật hơn để kiểm tra.
Louis15

Câu hỏi liên quan về việc tìm khoảng cách của một điểm đến một hình chữ nhật xoay . Đây là trường hợp suy biến của điều đó (chỉ kiểm tra khi khoảng cách bằng 0). Tất nhiên, sẽ có những tối ưu áp dụng ở đây mà không có ở đó.
Anko

Bạn đã xem xét việc xoay điểm vào khung tham chiếu của hình chữ nhật chưa?
Richard Tingle

@RichardTingle Thật ra lúc đầu tôi không có. Sau này tôi đã làm, vì tôi nghĩ rằng nó liên quan đến một trong những câu trả lời dưới đây. Nhưng chỉ cần làm rõ: trong những gì bạn đang đề xuất, sau khi xoay điểm vào khung tham chiếu của hình chữ nhật, thì ta nên kiểm tra sự bao gồm chỉ bằng cách so sánh logic giữa max.x, min.x, v.v?
Louis15

Câu trả lời:


2

Một tối ưu hóa dễ dàng và đơn giản sẽ là thay đổi điều kiện cuối cùng trong PointInTriangle:

bool PointInRectangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P) {
  ...
  if(u >= 0 && v >= 0 && u <= 1 && v <= 1)
      { return true; } else { return false; }
  }
}

mã đã có khá nhiều PointInRectangle, điều kiện (u + v) < 1là có để kiểm tra xem nó không nằm trong tam giác "thứ hai" của hình chữ nhật.

Ngoài ra, bạn cũng có thể thực hiện một isLeftthử nghiệm (ví dụ mã đầu tiên trên trang, cũng được giải thích rất nhiều) bốn lần và kiểm tra xem tất cả chúng có trả về kết quả với cùng một dấu hiệu hay không (điều này phụ thuộc vào việc các điểm được đưa ra theo chiều kim đồng hồ hay ngược chiều kim đồng hồ) điểm để được bên trong. Điều này làm việc cho bất kỳ đa giác lồi khác quá.

float isLeft( Point P0, Point P1, Point P2 )
{
    return ( (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y) );
}
bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
    return (isLeft(X, Y, P) > 0 && isLeft(Y, Z, P) > 0 && isLeft(Z, W, P) > 0 && isLeft(W, X, p) > 0);
}

Tuyệt vời. Tôi không biết nếu tôi thích đề xuất của bạn hơn, thực sự nhanh hơn và thanh lịch hơn nhiều so với đề xuất của tôi hoặc nếu tôi thích nhiều hơn mà bạn nhận thấy rằng mã PointInTri của tôi có thể dễ dàng trở thành PointInRec! Cảm ơn
Louis15

+1 cho isLeftphương thức. Nó không yêu cầu các chức năng trig (như Vector2.Dotvậy), giúp tăng tốc mọi thứ lên rất nhiều.
Anko

Btw, mã không thể được điều chỉnh (không kiểm tra; không có cách nào trong máy tính này), bằng cách đưa isLeft trực tiếp vào chức năng chính và bằng cách thay thế toán tử "&&" bằng "||" Máng logic nghịch đảo? public static bool PointInRectangle(Vector2 P, Vector2 X, Vector2 Y, Vector2 Z, Vector2 W) { return !(( (Y.x - X.x) * (P.y - X.y) - (P.x - X.x) * (Y.y - X.y) ) < 0 || ( (Z.x - Y.x) * (P.y - Y.y) - (P.x - Y.x) * (Z.y - Y.y) ) < 0 || ( (W.x - Z.x) * (P.y - Z.y) - (P.x - Z.x) * (W.y - Z.y) ) < 0 || ( (X.x - W.x) * (P.y - W.y) - (P.x - W.x) * (X.y - W.y) ) < 0 ); }
Louis15

1
@ Louis15 Tôi không nghĩ bạn cần - cả && và || sẽ ngừng thực hiện các tuyên bố tiếp theo nếu tìm thấy một tiêu cực / tích cực (hoặc có lý do nào khác không?). Khai báo isLeftlà nội tuyến, trình biên dịch sẽ làm điều gì đó tương tự cho bạn (và có lẽ tốt hơn bạn có thể, bởi vì các kỹ sư viết trình biên dịch biết CPU tốt nhất, chọn bất kỳ tùy chọn nào là nhanh nhất) làm cho mã của bạn dễ đọc hơn với hiệu ứng tương tự hoặc tốt hơn.
wonderra

8

Chỉnh sửa: Nhận xét của OP đã bị hoài nghi về hiệu quả của kiểm tra ràng buộc vòng tròn âm được đề xuất để cải thiện thuật toán để kiểm tra xem một điểm 2D tùy ý có nằm trong hình chữ nhật xoay và / hoặc di chuyển hay không. Loay hoay một chút trên công cụ trò chơi 2D của tôi (OpenGL / C ++), tôi bổ sung câu trả lời của mình bằng cách cung cấp điểm chuẩn hiệu suất của thuật toán của tôi so với thuật toán kiểm tra điểm hình chữ nhật hiện tại của OP (và các biến thể).

Ban đầu tôi đề nghị để nguyên thuật toán (vì nó gần như tối ưu), nhưng đơn giản hóa thông qua logic trò chơi đơn thuần: (1) sử dụng vòng tròn được xử lý trước xung quanh hình chữ nhật ban đầu; (2) thực hiện kiểm tra khoảng cách và nếu điểm nằm trong vòng tròn đã cho; (3) sử dụng OP hoặc bất kỳ thuật toán đơn giản nào khác (tôi khuyên bạn nên sử dụng thuật toán isLeft như được cung cấp trong câu trả lời khác). Logic đằng sau gợi ý của tôi là việc kiểm tra xem một điểm nằm trong vòng tròn có hiệu quả hơn đáng kể so với kiểm tra biên của hình chữ nhật xoay hoặc bất kỳ đa giác nào khác.

Kịch bản ban đầu của tôi cho một bài kiểm tra điểm chuẩn là chạy một số lượng lớn các chấm xuất hiện và biến mất (có vị trí thay đổi trong mỗi vòng lặp trò chơi) trong một không gian hạn chế sẽ được lấp đầy với khoảng 20 ô vuông xoay / di chuyển. Tôi đã xuất bản một video ( liên kết youtube ) cho mục đích minh họa. Lưu ý các tham số: số chấm xuất hiện ngẫu nhiên, số hoặc hình chữ nhật. Tôi sẽ điểm chuẩn với các thông số sau:

TẮT : Thuật toán đơn giản được cung cấp bởi OP mà không có kiểm tra âm biên giới

BẬT : Sử dụng các vòng tròn (ranh giới) được xử lý xung quanh các hình chữ nhật làm kiểm tra loại trừ đầu tiên

BẬT + Ngăn xếp : Tạo ranh giới vòng tròn trong thời gian chạy trong vòng lặp trên ngăn xếp

BẬT + Khoảng cách vuông : Sử dụng khoảng cách vuông làm tối ưu hóa hơn nữa để tránh sử dụng thuật toán căn bậc hai đắt tiền hơn (Pieter Geerkens).

Dưới đây là một bản tóm tắt về các hiệu suất khác nhau của các thuật toán khác nhau bằng cách hiển thị thời gian cần thiết để lặp qua vòng lặp.

nhập mô tả hình ảnh ở đây

Trục x cho thấy độ phức tạp tăng lên bằng cách thêm nhiều dấu chấm (và do đó làm chậm vòng lặp). (Ví dụ: tại 1000 điểm xuất hiện ngẫu nhiên kiểm tra trong một không gian có tâm với 20 hình chữ nhật, vòng lặp lặp và gọi thuật toán 20000 lần.) Trục y hiển thị thời gian (ms) để hoàn thành toàn bộ vòng lặp với độ phân giải cao hẹn giờ hiệu suất. Hơn 20 ms sẽ là vấn đề đối với một trò chơi tử tế vì nó sẽ không tận dụng lợi thế của khung hình / giây cao để nội suy một hình ảnh động mượt mà và trò chơi có thể xuất hiện do đó 'gồ ghề'.

Kết quả 1 : Thuật toán ràng buộc vòng tròn được xử lý trước với kiểm tra âm tính nhanh trong vòng lặp giúp cải thiện hiệu suất thêm 1900% so với thuật toán thông thường (5% thời gian của vòng lặp ban đầu mà không cần kiểm tra). Kết quả giữ tỷ lệ xấp xỉ với số lần lặp trong một vòng lặp, do đó không có vấn đề gì nếu chúng ta kiểm tra 10 hoặc 10000 điểm xuất hiện ngẫu nhiên. Do đó, trong hình minh họa này, người ta có thể tăng số lượng đối tượng một cách an toàn lên 10k mà không cảm thấy mất hiệu suất.

Kết quả 2 : Đã được đề xuất bởi một nhận xét trước đó rằng thuật toán có thể nhanh hơn nhưng tốn nhiều bộ nhớ. Tuy nhiên, lưu ý rằng việc lưu trữ một float cho kích thước vòng tròn được xử lý trước chỉ mất 4 byte. Điều này sẽ không gây ra vấn đề thực sự trừ khi OP có kế hoạch chạy đồng thời hơn 100000 đối tượng. Một cách tiếp cận hiệu quả và bộ nhớ là tính toán kích thước tối đa của vòng tròn trên ngăn xếp trong vòng lặp và để nó vượt ra khỏi phạm vi với mỗi lần lặp và do đó thực tế không sử dụng bộ nhớ cho một mức giá không xác định. Thật vậy, kết quả cho thấy phương pháp này thực sự chậm hơn so với sử dụng kích thước vòng tròn được xử lý trước, nhưng nó vẫn cho thấy sự cải thiện hiệu suất đáng kể khoảng 1150% (tức là 8% thời gian xử lý ban đầu).

Kết quả 3 : Tôi tiếp tục cải thiện thuật toán kết quả 1 bằng cách sử dụng khoảng cách bình phương thay vì khoảng cách thực tế và do đó thực hiện một phép toán căn bậc hai tính toán đắt tiền. Điều này chỉ giúp tăng hiệu suất (2400%). (Lưu ý: Tôi cũng thử các bảng băm cho các mảng được xử lý trước cho các xấp xỉ căn bậc hai với kết quả tương tự nhưng hơi tệ hơn)

Kết quả 4 : Tôi tiếp tục kiểm tra di chuyển / va chạm các hình chữ nhật xung quanh; tuy nhiên, điều này không thay đổi kết quả cơ bản (như mong đợi) vì kiểm tra logic về cơ bản vẫn giống nhau.

Kết quả 5 : Tôi thay đổi số lượng hình chữ nhật và thấy rằng thuật toán trở nên hiệu quả hơn nữa khi không gian được lấp đầy ít hơn (không được hiển thị trong bản demo). Kết quả cũng có phần được mong đợi, vì xác suất giảm cho một điểm xuất hiện trong không gian nhỏ giữa một vòng tròn và ranh giới của đối tượng. Mặt khác, tôi cố gắng tăng số lượng hình chữ nhật quá 100 trong cùng một không gian nhỏ hẹp và thay đổi kích thước chúng một cách linh hoạt tại thời gian chạy trong vòng lặp (sin (iterator)). Điều này vẫn thực hiện rất tốt với hiệu suất tăng thêm 570% (hoặc 15% thời gian vòng lặp ban đầu).

Kết quả 6 : Tôi kiểm tra các thuật toán thay thế được đề xuất ở đây và tìm thấy một sự khác biệt rất nhỏ nhưng không đáng kể về hiệu suất (2%). Thuật toán IsLeft thú vị và đơn giản hơn thực hiện rất tốt với hiệu suất tăng 17% (85% thời gian tính toán ban đầu) nhưng không ở đâu gần hiệu quả của thuật toán kiểm tra âm nhanh.

Quan điểm của tôi trước tiên là xem xét thiết kế tinh gọn và logic trò chơi, đặc biệt là khi xử lý các ranh giới và các sự kiện va chạm. Thuật toán hiện tại của OP đã khá hiệu quả và việc tối ưu hóa thêm không quan trọng bằng việc tối ưu hóa chính khái niệm cơ bản. Hơn nữa, thật tốt khi truyền đạt phạm vi và mục đích của trò chơi, vì hiệu quả của thuật toán phụ thuộc rất nhiều vào chúng.

Tôi đề nghị luôn cố gắng điểm chuẩn bất kỳ thuật toán phức tạp nào trong giai đoạn thiết kế trò chơi vì chỉ nhìn vào mã đơn giản có thể không tiết lộ sự thật về hiệu suất thời gian thực tế. Thuật toán được đề xuất có thể không cần thiết ở đây, ví dụ, nếu người ta chỉ muốn kiểm tra xem con trỏ chuột có nằm trong hình chữ nhật hay không, hoặc, khi phần lớn các đối tượng đã chạm vào. Nếu phần lớn các điểm kiểm tra nằm trong hình chữ nhật, thuật toán sẽ kém hiệu quả hơn. (Tuy nhiên, sau đó có thể thiết lập ranh giới 'vòng tròn bên trong' làm kiểm tra âm tính thứ cấp.) Kiểm tra ranh giới hình tròn / hình cầu rất hữu ích cho bất kỳ phát hiện va chạm nào của một số lượng lớn các vật thể có khoảng trống tự nhiên ở giữa chúng .

Rec Points  Iter    OFF     ON     ON_Stack     ON_SqrDist  Ileft Algorithm (Wondra)
            (ms)    (ms)    (ms)    (ms)        (ms)        (ms)
20  10      200     0.29    0.02    0.04        0.02        0.17
20  100     2000    2.23    0.10    0.20        0.09        1.69
20  1000    20000   24.48   1.25    1.99        1.05        16.95
20  10000   200000  243.85  12.54   19.61       10.85       160.58

Mặc dù tôi thích cách tiếp cận khác thường và yêu thích tài liệu tham khảo của Da Vinci, tôi không nghĩ rằng việc xử lý các vòng tròn, chứ đừng nói đến bán kính, sẽ hiệu quả. Ngoài ra, giải pháp đó chỉ hợp lý nếu tất cả các hình chữ nhật được cố định và biết trước
Louis15

Vị trí của hình chữ nhật không cần phải cố định. Sử dụng tọa độ tương đối. Hãy nghĩ về nó cũng như thế này. Bán kính đó vẫn giữ nguyên, không có vấn đề xoay vòng.
Majte

Đây là một câu trả lời tuyệt vời; vẫn tốt hơn bởi vì tôi đã không nghĩ về nó. Bạn có thể muốn lưu ý rằng việc sử dụng khoảng cách bình phương thay cho khoảng cách thực tế là đủ, tiết kiệm nhu cầu tính toán căn bậc hai.
Pieter Geerkens

Thuật toán thú vị để thử nghiệm tích cực / tiêu cực nhanh chóng! Vấn đề có thể là bộ nhớ thêm để lưu các vòng tròn giới hạn (và độ rộng) đã xử lý trước, nó có thể là heuristic tốt nhưng cũng lưu ý rằng nó có hạn sử dụng - chủ yếu cho các trường hợp bộ nhớ không quan trọng lắm (hình chữ nhật kích thước tĩnh trên các đối tượng lớn hơn = đối tượng trò chơi sprite) và có thời gian để tiền xử lý.
wonderra

Đã chỉnh sửa + thêm bài kiểm tra điểm chuẩn.
Majte

2

Xác định một hình chữ nhật có 4 điểm giúp tạo hình thang. Tuy nhiên, nếu bạn xác định nó theo x, y, chiều rộng, chiều cao và xoay quanh giữa của nó, bạn có thể xoay điểm bạn đang kiểm tra bằng cách xoay ngược hình chữ nhật của bạn (xung quanh cùng một gốc) và sau đó kiểm tra xem nó có trong hình chữ nhật ban đầu.


Hmm, cảm ơn vì lời đề nghị, nhưng xoay và nhận xoay ngược có vẻ không hiệu quả. Trên thực tế, nó sẽ khó hiệu quả như giải pháp của tôi - không đề cập đến wonderra
Louis15

Bạn có thể lưu ý rằng việc xoay một điểm 3D với một ma trận là 6 phép nhân và 3 phép cộng và một lệnh gọi hàm. Giải pháp của @ wonderra là tương đương tốt nhất, nhưng ít rõ ràng hơn trong ý định; và dễ bị lỗi bảo trì hơn thông qua việc vi phạm DRY
Pieter Geerkens

@Pieter Geerkens xen kẽ yêu cầu, làm thế nào để bất kỳ giải pháp nào của tôi vi phạm DRY (và DRY là một trong những nguyên tắc lập trình chính? Chưa bao giờ nghe về nó cho đến bây giờ)? Và quan trọng nhất, những giải pháp đó có lỗi gì? Luôn sẵn sàng học hỏi.
wonderra

@wondra: DRY = Đừng lặp lại chính mình. Đoạn mã của bạn đề nghị mã hóa các chi tiết của ma trận bằng cách nhân vectơ ở mọi nơi mà chức năng xuất hiện trong mã thay vì gọi một phương thức ma trận ứng dụng ma trận tiêu chuẩn.
Pieter Geerkens

@PieterGeerkens tất nhiên nó chỉ đề xuất một phần của nó - 1) bạn không có ma trận rõ ràng (phân bổ ma trận mới cho mỗi truy vấn sẽ ảnh hưởng đến hiệu năng) 2) Tôi chỉ sử dụng trường hợp nhân cụ thể, được tối ưu hóa cho trường hợp này. một. Đây là hoạt động cấp thấp và cần được đóng gói để ngăn chặn hành vi không mong muốn.
wonderra

1

Tôi không có thời gian để điểm chuẩn này, nhưng đề nghị của tôi sẽ là lưu trữ ma trận biến đổi biến hình chữ nhật thành hình vuông được căn chỉnh theo trục trong phạm vi x và y từ 0 đến 1. Nói cách khác, lưu trữ ma trận biến một góc của hình chữ nhật thành (0,0) và góc đối diện thành (1,1).

Điều này tất nhiên sẽ tốn kém hơn nếu hình chữ nhật được di chuyển nhiều và sự va chạm được kiểm tra khá hiếm khi, nhưng nếu có nhiều kiểm tra hơn so với cập nhật cho hình chữ nhật thì ít nhất sẽ nhanh hơn phương pháp thử nghiệm ban đầu đối với hai hình tam giác, vì sáu sản phẩm chấm sẽ được thay thế bằng một phép nhân ma trận.

Nhưng như mọi khi tốc độ của thuật toán này phụ thuộc rất nhiều vào loại kiểm tra mà bạn mong đợi sẽ được thực hiện. Nếu hầu hết các điểm thậm chí không gần với hình chữ nhật thực hiện kiểm tra khoảng cách đơn giản (ví dụ (point.x - firstCorner.x)> aLargeDistance) có thể dẫn đến tăng tốc lớn, trong khi nó thậm chí có thể làm chậm mọi thứ nếu gần như tất cả các điểm nằm bên trong hình chữ nhật.

EDIT: Đây là lớp hình chữ nhật của tôi sẽ như thế nào:

class Rectangle
{
public:
    Matrix3x3 _transform;

    Rectangle()
    {}

    void setCorners(Vector2 p_a, Vector2 p_b, Vector2 p_c)
    {
        // create a matrix from the two edges of the rectangle
        Vector2 edgeX = p_b - p_a;
        Vector2 edgeY = p_c - p_a;

        // and then create the inverse of that matrix because we want to 
        // transform points from world coordinates into "rectangle coordinates".
        float scaling = 1/(edgeX._x*edgeY._y - edgeY._x*edgeX._y);

        _transform._columns[0]._x = scaling * edgeY._y;
        _transform._columns[0]._y = - scaling * edgeX._y;
        _transform._columns[1]._x = - scaling * edgeY._x;
        _transform._columns[1]._y = scaling * edgeX._x;

        // the third column is the translation, which also has to be transformed into "rectangle space"
        _transform._columns[2]._x = -p_a._x * _transform._columns[0]._x - p_a._y * _transform._columns[1]._x;
        _transform._columns[2]._y = -p_a._x * _transform._columns[0]._y - p_a._y * _transform._columns[1]._y;
    }

    bool isInside(Vector2 p_point)
    {
        Vector2 test = _transform.transform(p_point);
        return  (test._x>=0)
                && (test._x<=1)
                && (test._y>=0)
                && (test._y<=1);
    }
};

Đây là danh sách đầy đủ của điểm chuẩn của tôi:

#include <cstdlib>
#include <math.h>
#include <iostream>

#include <sys/time.h>

using namespace std;

class Vector2
{
public:
    float _x;
    float _y;

    Vector2()
    :_x(0)
    ,_y(0)
    {}

    Vector2(float p_x, float p_y)
        : _x (p_x)
        , _y (p_y)
        {}

    Vector2 operator-(const Vector2& p_other) const
    {
        return Vector2(_x-p_other._x, _y-p_other._y);
    }

    Vector2 operator+(const Vector2& p_other) const
    {
        return Vector2(_x+p_other._x, _y+p_other._y);
    }

    Vector2 operator*(float p_factor) const
    {
        return Vector2(_x*p_factor, _y*p_factor);
    }

    static float Dot(Vector2 p_a, Vector2 p_b)
    {
        return (p_a._x*p_b._x + p_a._y*p_b._y);
    }
};

bool PointInTriangle(Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
 // Compute vectors        
 Vector2 v0 = C - A;
 Vector2 v1 = B - A;
 Vector2 v2 = P - A;

 // Compute dot products
 float dot00 = Vector2::Dot(v0, v0);
 float dot01 = Vector2::Dot(v0, v1);
 float dot02 = Vector2::Dot(v0, v2);
 float dot11 = Vector2::Dot(v1, v1);
 float dot12 = Vector2::Dot(v1, v2);

 // Compute barycentric coordinates
 float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
 float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
 float v = (dot00 * dot12 - dot01 * dot02) * invDenom;

 // Check if point is in triangle
 if(u >= 0 && v >= 0 && (u + v) < 1)
    { return true; } else { return false; }
}


bool PointInRectangle(Vector2 X, Vector2 Y, Vector2 Z, Vector2 W, Vector2 P)
{
 if(PointInTriangle(X,Y,Z,P)) return true;
 if(PointInTriangle(X,Z,W,P)) return true;
 return false;
}

class Matrix3x3
{
public:
    Vector2 _columns[3];

    Vector2 transform(Vector2 p_in)
    {
        return _columns[0] * p_in._x + _columns[1] * p_in._y + _columns[2];
    }
};

class Rectangle
{
public:
    Matrix3x3 _transform;

    Rectangle()
    {}

    void setCorners(Vector2 p_a, Vector2 p_b, Vector2 p_c)
    {
        // create a matrix from the two edges of the rectangle
        Vector2 edgeX = p_b - p_a;
        Vector2 edgeY = p_c - p_a;

        // and then create the inverse of that matrix because we want to 
        // transform points from world coordinates into "rectangle coordinates".
        float scaling = 1/(edgeX._x*edgeY._y - edgeY._x*edgeX._y);

        _transform._columns[0]._x = scaling * edgeY._y;
        _transform._columns[0]._y = - scaling * edgeX._y;
        _transform._columns[1]._x = - scaling * edgeY._x;
        _transform._columns[1]._y = scaling * edgeX._x;

        // the third column is the translation, which also has to be transformed into "rectangle space"
        _transform._columns[2]._x = -p_a._x * _transform._columns[0]._x - p_a._y * _transform._columns[1]._x;
        _transform._columns[2]._y = -p_a._x * _transform._columns[0]._y - p_a._y * _transform._columns[1]._y;
    }

    bool isInside(Vector2 p_point)
    {
        Vector2 test = _transform.transform(p_point);
        return  (test._x>=0)
                && (test._x<=1)
                && (test._y>=0)
                && (test._y<=1);
    }
};

void runTest(float& outA, float& outB)
{
    Rectangle r;
    r.setCorners(Vector2(0,0.5), Vector2(0.5,1), Vector2(0.5,0));

    int numTests = 10000;

    Vector2 points[numTests];

    Vector2 cornerA[numTests];
    Vector2 cornerB[numTests];
    Vector2 cornerC[numTests];
    Vector2 cornerD[numTests];

    bool results[numTests];
    bool resultsB[numTests];

    for (int i=0; i<numTests; ++i)
    {
        points[i]._x = rand() / ((float)RAND_MAX);
        points[i]._y = rand() / ((float)RAND_MAX);

        cornerA[i]._x = rand() / ((float)RAND_MAX);
        cornerA[i]._y = rand() / ((float)RAND_MAX);

        Vector2 edgeA;
        edgeA._x = rand() / ((float)RAND_MAX);
        edgeA._y = rand() / ((float)RAND_MAX);

        Vector2 edgeB;
        edgeB._x = rand() / ((float)RAND_MAX);
        edgeB._y = rand() / ((float)RAND_MAX);

        cornerB[i] = cornerA[i] + edgeA;
        cornerC[i] = cornerA[i] + edgeB;
        cornerD[i] = cornerA[i] + edgeA + edgeB;
    }

    struct timeval start, end;

    gettimeofday(&start, NULL);
    for (int i=0; i<numTests; ++i)
    {
        r.setCorners(cornerA[i], cornerB[i], cornerC[i]);
        results[i] = r.isInside(points[i]);
    }
    gettimeofday(&end, NULL);
    float elapsed = (end.tv_sec - start.tv_sec)*1000;
    elapsed += (end.tv_usec - start.tv_usec)*0.001;
    outA += elapsed;

    gettimeofday(&start, NULL);
    for (int i=0; i<numTests; ++i)
    {
        resultsB[i] = PointInRectangle(cornerA[i], cornerB[i], cornerC[i], cornerD[i], points[i]);
    }
    gettimeofday(&end, NULL);
    elapsed = (end.tv_sec - start.tv_sec)*1000;
    elapsed += (end.tv_usec - start.tv_usec)*0.001;
    outB += elapsed;
}

/*
 * 
 */
int main(int argc, char** argv) 
{
    float a = 0;
    float b = 0;

    for (int i=0; i<5000; i++)
    {
        runTest(a, b);
    }

    std::cout << "Result: " << a << " / " << b << std::endl;

    return 0;
}

Mã chắc chắn không đẹp, nhưng tôi không thấy bất kỳ lỗi lớn nào. Với mã đó, tôi nhận được kết quả chỉ ra rằng giải pháp của tôi nhanh gấp khoảng hai lần nếu hình chữ nhật được di chuyển giữa mỗi lần kiểm tra. Nếu nó không di chuyển thì mã của tôi dường như nhanh hơn gấp năm lần.

Nếu bạn biết cách mã sẽ được sử dụng, bạn thậm chí có thể tăng tốc thêm một chút bằng cách tách biến đổi và kiểm tra thành hai chiều. Ví dụ, trong một trò chơi đua xe, có thể sẽ nhanh hơn để kiểm tra tọa độ trước khi chỉ vào hướng lái xe, bởi vì nhiều chướng ngại vật sẽ ở phía trước hoặc phía sau xe, nhưng hầu như không có bên phải hoặc bên trái của nó.


Thú vị, nhưng đừng quên bạn cũng cần áp dụng xoay vòng ma trận trên các dấu chấm. Tôi có một hoạt động quay ma trận trong gameengine của tôi và có thể điểm chuẩn thuật toán của bạn sau này. Liên quan đến bình luận cuối cùng của bạn. Sau đó, bạn cũng có thể có một 'vòng tròn bên trong' được xác định và thực hiện kiểm tra âm tính kép nếu dấu chấm nằm bên ngoài vòng tròn bên trong và bên trong vòng tròn bên ngoài như mô tả ở trên.
Majte

Vâng, điều đó sẽ giúp nếu bạn mong đợi hầu hết các điểm sẽ ở gần giữa của tam giác. Tôi đang tưởng tượng một tình huống như một đường đua hình chữ nhật trong đó bạn ví dụ xác định một con đường hình chữ nhật bằng cách sử dụng một hình chữ nhật bên ngoài mà nhân vật phải ở bên trong và một hình chữ nhật bên trong nhỏ hơn mà nó phải ở ngoài. Trong trường hợp đó, mọi kiểm tra sẽ gần với đường viền của hình chữ nhật và những kiểm tra vòng tròn đó có lẽ chỉ làm cho hiệu suất kém hơn. Cấp, đó là một ví dụ được xây dựng, nhưng tôi muốn nói rằng đó là điều có thể thực sự xảy ra.
Lars Kokemohr

Những điều như thế có thể xảy ra, vâng. Tôi tự hỏi đâu là điểm ngọt ngào để chống lại thuật toán. Cuối cùng, nó sôi sục với mục đích của bạn. Nếu bạn có thời gian, bạn có thể đăng mã của mình không, bằng cách sử dụng bài OP và tôi có thể điểm chuẩn thuật toán của bạn không? Hãy xem liệu trực giác của bạn có đúng không. Tôi tò mò về hiệu suất của ý tưởng của bạn đối với Thuật toán IsLeft.
Majte
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.