我没有时间对此进行基准测试,但是我的建议是存储将矩阵转换为从0到1的x和y范围内的矩形轴对齐正方形的转换矩阵。换句话说,存储矩阵将矩形的一个角转换为(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;
}
该代码当然不是很漂亮,但是我没有立即看到任何主要的错误。使用该代码,我得到的结果表明,如果在每次检查之间移动矩形,我的解决方案的速度大约是原来的两倍。如果它不动,那么我的代码似乎快了五倍以上。
如果您知道代码的使用方式,则可以通过将转换和检查分为两个维度来甚至进一步提高代码速度。例如,在赛车游戏中,首先检查指向行驶方向的坐标可能会更快,因为许多障碍物会在汽车的前面或后面,但几乎没有障碍物会在它的右边或左边。