ฉันไม่มีเวลาในการเปรียบเทียบสิ่งนี้ แต่ข้อเสนอแนะของฉันคือการเก็บเมทริกซ์การแปลงที่แปลงสี่เหลี่ยมผืนผ้าเป็นสี่เหลี่ยมจตุรัสแกนใน x- และ y- ช่วงจาก 0 ถึง 1 ในคำอื่น ๆ เก็บเมทริกซ์ที่ แปลงมุมหนึ่งของสี่เหลี่ยมเป็น (0,0) และอีกมุมหนึ่งเป็น (1,1)
แน่นอนว่าจะมีราคาแพงกว่าถ้าสี่เหลี่ยมถูกย้ายบ่อยครั้งและมีการตรวจสอบการชนค่อนข้างบ่อย แต่ถ้ามีการตรวจสอบมากกว่าการอัพเดทสี่เหลี่ยมอย่างน้อยก็เร็วกว่าวิธีทดสอบแบบสามเหลี่ยมสองรูปแบบเดิมอย่างน้อย เนื่องจากผลิตภัณฑ์หกจุดจะถูกแทนที่ด้วยการคูณเมทริกซ์เดียว
แต่เช่นเคยความเร็วของอัลกอริทึมนี้ขึ้นอยู่กับชนิดของการตรวจสอบที่คุณคาดว่าจะทำ หากจุดส่วนใหญ่ไม่ได้อยู่ใกล้กับสี่เหลี่ยมผืนผ้าที่ดำเนินการตรวจสอบระยะทางง่ายๆ (เช่น (point.x - firstCorner.x)> aLargeDistance) อาจส่งผลให้มีการเร่งความเร็วขนาดใหญ่ในขณะที่มันอาจทำให้ช้าลงหากเกือบทั้งหมด จุดที่อยู่ภายในสี่เหลี่ยม
แก้ไข: นี่คือสิ่งที่ชั้นสี่เหลี่ยมผืนผ้าของฉันจะมีลักษณะ:
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);
}
};
นี่คือรายการเต็มรูปแบบของมาตรฐานของฉัน:
#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;
}
รหัสไม่สวย แต่ฉันไม่เห็นข้อบกพร่องที่สำคัญทันที เมื่อใช้รหัสนั้นฉันจะได้ผลลัพธ์ที่ระบุว่าโซลูชันของฉันเร็วเป็นสองเท่าหากสี่เหลี่ยมถูกย้ายระหว่างการตรวจสอบแต่ละครั้ง หากไม่ย้ายรหัสของฉันน่าจะเร็วกว่าห้าเท่า
หากคุณรู้ว่าจะใช้รหัสอย่างไรคุณสามารถเพิ่มความเร็วได้อีกเล็กน้อยโดยแยกการแปลงและการตรวจสอบออกเป็นสองมิติ ยกตัวอย่างเช่นในเกมแข่งรถก็อาจจะเร็วขึ้นเพื่อตรวจสอบประสานงานแรกที่จุดในทิศทางที่ขับรถเพราะอุปสรรคมากมายจะอยู่ในด้านหน้าหรือด้านหลังรถ แต่แทบจะไม่ได้จะซ้ายหรือขวาของมัน