Je n'ai pas eu le temps de comparer cela, mais ma suggestion serait de stocker la matrice de transformation qui transforme le rectangle en carré aligné sur l'axe dans la plage x et y de 0 à 1. En d'autres termes, stocker la matrice qui transforme un coin du rectangle en (0,0) et l'autre en (1,1).
Cela serait bien sûr plus cher si le rectangle est déplacé beaucoup et que la collision est vérifiée assez rarement, mais s'il y a beaucoup plus de vérifications que de mises à jour du rectangle, ce serait au moins plus rapide que l'approche initiale de test contre deux triangles, car les six produits scalaires seraient remplacés par une multiplication matricielle.
Mais comme toujours, la vitesse de cet algorithme dépend beaucoup du type de contrôles que vous prévoyez d'effectuer. Si la plupart des points ne sont même pas proches du rectangle, une simple vérification de distance (par exemple (point.x - firstCorner.x)> aLargeDistance) peut entraîner une grande accélération, alors qu'elle peut même ralentir les choses si presque tout les points sont à l'intérieur du rectangle.
EDIT: Voici à quoi ressemblerait ma classe Rectangle:
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);
}
};
Voici la liste complète de mon benchmark:
#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;
}
Le code n'est certainement pas beau, mais je ne vois pas immédiatement de bugs majeurs. Avec ce code, j'obtiens des résultats qui indiquent que ma solution est environ deux fois plus rapide si le rectangle est déplacé entre chaque vérification. S'il ne bouge pas, mon code semble être plus de cinq fois plus rapide.
Si vous savez comment le code va être utilisé, vous pouvez même l'accélérer un peu plus en séparant la transformation et les contrôles dans les deux dimensions. Par exemple, dans un jeu de course, il serait probablement plus rapide de vérifier d'abord les coordonnées qui pointent dans la direction de la conduite, car de nombreux obstacles seront devant ou derrière la voiture, mais presque aucun ne sera à droite ou à gauche.