分离轴定理:绕质心旋转
Separating axis theorem: rotation around center of mass
问题在Polygon::FindAxisLeastPenetration
:
double Polygon::FindAxisLeastPenetration(unsigned int *faceIndex, const Polygon &polygonA, const Polygon &polygonB) const {
double bestDistance = -std::numeric_limits<double>::infinity();
unsigned int bestIndex;
for (unsigned int i = 0; i < polygonA.points.size(); i++) {
Vector2D n = polygonA.normals[i];
Vector2D nw = polygonA.rotationMatrix * n; //ROTATION
Matrix22 buT = polygonB.rotationMatrix.Transposed();
n = buT * nw; //ROTATION
Vector2D support = polygonB.points[polygonB.GetSupport(-n)];
Vector2D vertex = polygonA.points[i];
vertex = polygonA.rotationMatrix * vertex; //ROTATION
vertex.Add(polygonA.body->GetPosition());
vertex.Subtract(polygonB.body->GetPosition());
vertex = buT * vertex; // ROTATION
double distance = n.DotProduct(support - vertex);
if (distance > bestDistance) {
bestDistance = distance;
bestIndex = i;
}
}
*faceIndex = bestIndex;
return bestDistance;
}
unsigned int Polygon::GetSupport(const Vector2D &dir) const {
double bestProjection = -std::numeric_limits<double>::infinity();
unsigned int bestIndex = 0;
for (unsigned int i = 0; i < points.size(); i++) {
Vector2D vertex = points[i];
double projection = vertex.DotProduct(dir);
if (projection > bestProjection) {
bestProjection = projection;
bestIndex = i;
}
}
return bestIndex;
}
Manifold Polygon::CheckCollision(const Polygon &polygonA, const Polygon &polygonB) const {
Manifold result;
result.objectA = polygonA.body;
result.objectB = polygonB.body;
unsigned int indexA;
double penetrationA = Polygon::FindAxisLeastPenetration(&indexA, polygonA, polygonB);
if (penetrationA >= 0.0) {
result.intersects = false;
return result;
}
unsigned int indexB;
double penetrationB = Polygon::FindAxisLeastPenetration(&indexB, polygonB, polygonA);
if (penetrationB >= 0.0) {
result.intersects = false;
return result;
}
result.intersects = true;
//...
return result;
Rectangle::Rectangle(double width, double height) : Polygon() {
double hw = width / 2.0;
double hh = height / 2.0;
points.push_back(Vector2D(-hw, -hh));
points.push_back(Vector2D(hw, -hh));
points.push_back(Vector2D(hw, hh));
points.push_back(Vector2D(-hw, hh));
// points.push_back(Vector2D(0, 0));
// points.push_back(Vector2D(width, 0));
// points.push_back(Vector2D(width, height));
// points.push_back(Vector2D(0, height));
normals.push_back(Vector2D(0.0, -1.0));
normals.push_back(Vector2D(1.0, 0.0));
normals.push_back(Vector2D(0.0, 1.0));
normals.push_back(Vector2D(-1.0, 0.0));
center.x = 0;
center.y = 0;
}
polygon.rotationMatrix
是一个 Matrix22
类型的对象,它是一个 2x2 矩阵。
polygon.points
是 std::vector<Vector2D>
填充向量。
polygon.body
是指向 Object
实例的指针。在这种情况下,它仅用于获取位置。
polygon.body->position
是 Vector2D
的实例,包含 X
和 Y
坐标。
Vector2D polygon.body->GetPosition()
returns 物体的位置向量。
它工作正常,除了旋转是围绕 [0, 0]
点完成的
但它应该围绕质心旋转。
我知道围绕一个点的旋转可以这样完成:
rotationMatrix * (vertex - point) + point
渲染多边形时效果很好。
但不在碰撞检测中。
在这种情况下,如何围绕特定点旋转矢量?
编辑:这是我目前所拥有的
double Polygon::FindAxisLeastPenetration(unsigned int *faceIndex, const Polygon &polygonA, const Polygon &polygonB) const {
double bestDistance = -std::numeric_limits<double>::infinity();
unsigned int bestIndex;
for (unsigned int i = 0; i < polygonA.points.size(); i++) {
// Calculate normal
unsigned int j = i == points.size() ? 0 : i + 1;
Vector2D n;
// Rotate points
Vector2D p1 = polygonA.rotationMatrix * (polygonA.points[i] - polygonA.Center()) + polygonA.Center();
Vector2D p2 = polygonA.rotationMatrix * (polygonA.points[j] - polygonA.Center()) + polygonA.Center();
n.x = p2.y - p1.y;
n.y = -(p2.x - p1.x);
n.Normalize();
Vector2D support = polygonB.points[polygonB.GetSupport(-n)];
support = polygonB.rotationMatrix * (support - polygonB.Center()) + polygonB.Center();
support.Add(polygonB.body->GetPosition());
Vector2D vertex = polygonA.points[i];
vertex = polygonA.rotationMatrix * (vertex - polygonA.Center()) + polygonA.Center(); //ROTATION
vertex.Add(polygonA.body->GetPosition());
double distance = n.DotProduct(support - vertex);
if (distance > bestDistance) {
bestDistance = distance;
bestIndex = i;
}
}
*faceIndex = bestIndex;
return bestDistance;
}
unsigned int Polygon::GetSupport(const Vector2D &dir) const {
double bestProjection = -std::numeric_limits<double>::infinity();
unsigned int bestIndex = 0;
for (unsigned int i = 0; i < points.size(); i++) {
Vector2D vertex = rotationMatrix * (points[i] - center) + center;
double projection = vertex.DotProduct(dir);
if (projection > bestProjection) {
bestProjection = projection;
bestIndex = i;
}
}
return bestIndex;
}
现在我不太关心优化。还是有同样的问题。围绕中心旋转时,未正确检测到碰撞。但是,如果中心是 [0, 0]
或未使用,则碰撞检测可以正常工作,但旋转再次执行错误。
编辑:即使在碰撞检测之前旋转,我也会遇到同样的问题。
迄今为止最好的方法是平移多边形,使其中心位于 [0, 0]
,但在某些角度未检测到碰撞。不知道现在要做什么。
编辑:截图(正在平移多边形以使其质心始终位于 [0, 0]
,在这种情况下多边形是矩形)
碰撞检测在这里效果不佳
碰撞检测在这里也不能正常工作
碰撞检测在这里运行良好
编辑:我添加了 Rectangle
class.
无论多边形原点是否与重心对齐,这都应该有效。我将从最重要的内容开始,并以已更改的支持方法结束。
编辑: 修订实施。
struct Response {
Response()
: overlap(std::numeric_limits<double>::max()) {}
Vector2D axis;
double overlap;
};
bool FindAxisLeastPenetration(const Polygon& a, const Polygon& b,
Response* response)
{
for ( unsigned long i = 0; i < a.points.size(); i++ )
{
Vector2D axis = a.normals[i];
Vector2D support = b.GetSupport(-axis);
double overlap = axis.DotProduct(a.points[i] - support);
if (overlap <= 0.0)
return false;
if (overlap < response->overlap)
{
response->overlap = overlap;
response->axis = axis;
}
}
return true;
}
bool CheckCollisionLocal(const Polygon& a, const Polygon& b,
Vector2D* min_translation)
// @note assumes untransformed polygons.
{
Polygon worldA = a.ToWorld();
Polygon worldB = b.ToWorld();
Response responseA;
Response responseB;
if (!FindAxisLeastPenetration(worldA, worldB, &responseA))
return false;
if (!FindAxisLeastPenetration(worldB, worldA, &responseB))
return false;
if (responseA.overlap <= responseB.overlap)
*min_translation = responseA.axis * responseA.overlap;
else
*min_translation = -responseB.axis * responseB.overlap;
return true;
}
用例,
bool HandleCollisionLocal(Polygon& a, Polygon& b)
{
Vector2D translation;
if (!CheckCollisionLocal(a, b, &translation))
return false;
if (MOVE_POLYGON_A)
a.body.SetPosition(a.body.GetPosition() - translation);
else
b.body.SetPosition(b.body.GetPosition() + translation);
return true;
}
支持,
Polygon Polygon::ToWorld() const
{
Polygon result = *this;
for ( auto& point : result.points )
{
point = result.rotationMatrix * point;
point.Add(result.body.GetPosition());
}
for ( auto& normal : result.normals )
normal = result.rotationMatrix * normal;
return result;
}
Vector2D Polygon::GetSupport(const Vector2D& direction) const
{
double best_projection = -std::numeric_limits<double>::max();
Vector2D best_point;
for ( auto point : points )
{
double projection = point.DotProduct(direction);
if (projection > best_projection)
{
best_projection = projection;
best_point = point;
}
}
return best_point;
}
注意:此版本反转翻译向量以符合 - 似乎是 - 标准。
问题在Polygon::FindAxisLeastPenetration
:
double Polygon::FindAxisLeastPenetration(unsigned int *faceIndex, const Polygon &polygonA, const Polygon &polygonB) const {
double bestDistance = -std::numeric_limits<double>::infinity();
unsigned int bestIndex;
for (unsigned int i = 0; i < polygonA.points.size(); i++) {
Vector2D n = polygonA.normals[i];
Vector2D nw = polygonA.rotationMatrix * n; //ROTATION
Matrix22 buT = polygonB.rotationMatrix.Transposed();
n = buT * nw; //ROTATION
Vector2D support = polygonB.points[polygonB.GetSupport(-n)];
Vector2D vertex = polygonA.points[i];
vertex = polygonA.rotationMatrix * vertex; //ROTATION
vertex.Add(polygonA.body->GetPosition());
vertex.Subtract(polygonB.body->GetPosition());
vertex = buT * vertex; // ROTATION
double distance = n.DotProduct(support - vertex);
if (distance > bestDistance) {
bestDistance = distance;
bestIndex = i;
}
}
*faceIndex = bestIndex;
return bestDistance;
}
unsigned int Polygon::GetSupport(const Vector2D &dir) const {
double bestProjection = -std::numeric_limits<double>::infinity();
unsigned int bestIndex = 0;
for (unsigned int i = 0; i < points.size(); i++) {
Vector2D vertex = points[i];
double projection = vertex.DotProduct(dir);
if (projection > bestProjection) {
bestProjection = projection;
bestIndex = i;
}
}
return bestIndex;
}
Manifold Polygon::CheckCollision(const Polygon &polygonA, const Polygon &polygonB) const {
Manifold result;
result.objectA = polygonA.body;
result.objectB = polygonB.body;
unsigned int indexA;
double penetrationA = Polygon::FindAxisLeastPenetration(&indexA, polygonA, polygonB);
if (penetrationA >= 0.0) {
result.intersects = false;
return result;
}
unsigned int indexB;
double penetrationB = Polygon::FindAxisLeastPenetration(&indexB, polygonB, polygonA);
if (penetrationB >= 0.0) {
result.intersects = false;
return result;
}
result.intersects = true;
//...
return result;
Rectangle::Rectangle(double width, double height) : Polygon() {
double hw = width / 2.0;
double hh = height / 2.0;
points.push_back(Vector2D(-hw, -hh));
points.push_back(Vector2D(hw, -hh));
points.push_back(Vector2D(hw, hh));
points.push_back(Vector2D(-hw, hh));
// points.push_back(Vector2D(0, 0));
// points.push_back(Vector2D(width, 0));
// points.push_back(Vector2D(width, height));
// points.push_back(Vector2D(0, height));
normals.push_back(Vector2D(0.0, -1.0));
normals.push_back(Vector2D(1.0, 0.0));
normals.push_back(Vector2D(0.0, 1.0));
normals.push_back(Vector2D(-1.0, 0.0));
center.x = 0;
center.y = 0;
}
polygon.rotationMatrix
是一个 Matrix22
类型的对象,它是一个 2x2 矩阵。
polygon.points
是 std::vector<Vector2D>
填充向量。
polygon.body
是指向 Object
实例的指针。在这种情况下,它仅用于获取位置。
polygon.body->position
是 Vector2D
的实例,包含 X
和 Y
坐标。
Vector2D polygon.body->GetPosition()
returns 物体的位置向量。
它工作正常,除了旋转是围绕 [0, 0]
点完成的
但它应该围绕质心旋转。
我知道围绕一个点的旋转可以这样完成:
rotationMatrix * (vertex - point) + point
渲染多边形时效果很好。 但不在碰撞检测中。
在这种情况下,如何围绕特定点旋转矢量?
编辑:这是我目前所拥有的
double Polygon::FindAxisLeastPenetration(unsigned int *faceIndex, const Polygon &polygonA, const Polygon &polygonB) const {
double bestDistance = -std::numeric_limits<double>::infinity();
unsigned int bestIndex;
for (unsigned int i = 0; i < polygonA.points.size(); i++) {
// Calculate normal
unsigned int j = i == points.size() ? 0 : i + 1;
Vector2D n;
// Rotate points
Vector2D p1 = polygonA.rotationMatrix * (polygonA.points[i] - polygonA.Center()) + polygonA.Center();
Vector2D p2 = polygonA.rotationMatrix * (polygonA.points[j] - polygonA.Center()) + polygonA.Center();
n.x = p2.y - p1.y;
n.y = -(p2.x - p1.x);
n.Normalize();
Vector2D support = polygonB.points[polygonB.GetSupport(-n)];
support = polygonB.rotationMatrix * (support - polygonB.Center()) + polygonB.Center();
support.Add(polygonB.body->GetPosition());
Vector2D vertex = polygonA.points[i];
vertex = polygonA.rotationMatrix * (vertex - polygonA.Center()) + polygonA.Center(); //ROTATION
vertex.Add(polygonA.body->GetPosition());
double distance = n.DotProduct(support - vertex);
if (distance > bestDistance) {
bestDistance = distance;
bestIndex = i;
}
}
*faceIndex = bestIndex;
return bestDistance;
}
unsigned int Polygon::GetSupport(const Vector2D &dir) const {
double bestProjection = -std::numeric_limits<double>::infinity();
unsigned int bestIndex = 0;
for (unsigned int i = 0; i < points.size(); i++) {
Vector2D vertex = rotationMatrix * (points[i] - center) + center;
double projection = vertex.DotProduct(dir);
if (projection > bestProjection) {
bestProjection = projection;
bestIndex = i;
}
}
return bestIndex;
}
现在我不太关心优化。还是有同样的问题。围绕中心旋转时,未正确检测到碰撞。但是,如果中心是 [0, 0]
或未使用,则碰撞检测可以正常工作,但旋转再次执行错误。
编辑:即使在碰撞检测之前旋转,我也会遇到同样的问题。
迄今为止最好的方法是平移多边形,使其中心位于 [0, 0]
,但在某些角度未检测到碰撞。不知道现在要做什么。
编辑:截图(正在平移多边形以使其质心始终位于 [0, 0]
,在这种情况下多边形是矩形)
碰撞检测在这里效果不佳
碰撞检测在这里也不能正常工作
碰撞检测在这里运行良好
编辑:我添加了 Rectangle
class.
无论多边形原点是否与重心对齐,这都应该有效。我将从最重要的内容开始,并以已更改的支持方法结束。
编辑: 修订实施。
struct Response {
Response()
: overlap(std::numeric_limits<double>::max()) {}
Vector2D axis;
double overlap;
};
bool FindAxisLeastPenetration(const Polygon& a, const Polygon& b,
Response* response)
{
for ( unsigned long i = 0; i < a.points.size(); i++ )
{
Vector2D axis = a.normals[i];
Vector2D support = b.GetSupport(-axis);
double overlap = axis.DotProduct(a.points[i] - support);
if (overlap <= 0.0)
return false;
if (overlap < response->overlap)
{
response->overlap = overlap;
response->axis = axis;
}
}
return true;
}
bool CheckCollisionLocal(const Polygon& a, const Polygon& b,
Vector2D* min_translation)
// @note assumes untransformed polygons.
{
Polygon worldA = a.ToWorld();
Polygon worldB = b.ToWorld();
Response responseA;
Response responseB;
if (!FindAxisLeastPenetration(worldA, worldB, &responseA))
return false;
if (!FindAxisLeastPenetration(worldB, worldA, &responseB))
return false;
if (responseA.overlap <= responseB.overlap)
*min_translation = responseA.axis * responseA.overlap;
else
*min_translation = -responseB.axis * responseB.overlap;
return true;
}
用例,
bool HandleCollisionLocal(Polygon& a, Polygon& b)
{
Vector2D translation;
if (!CheckCollisionLocal(a, b, &translation))
return false;
if (MOVE_POLYGON_A)
a.body.SetPosition(a.body.GetPosition() - translation);
else
b.body.SetPosition(b.body.GetPosition() + translation);
return true;
}
支持,
Polygon Polygon::ToWorld() const
{
Polygon result = *this;
for ( auto& point : result.points )
{
point = result.rotationMatrix * point;
point.Add(result.body.GetPosition());
}
for ( auto& normal : result.normals )
normal = result.rotationMatrix * normal;
return result;
}
Vector2D Polygon::GetSupport(const Vector2D& direction) const
{
double best_projection = -std::numeric_limits<double>::max();
Vector2D best_point;
for ( auto point : points )
{
double projection = point.DotProduct(direction);
if (projection > best_projection)
{
best_projection = projection;
best_point = point;
}
}
return best_point;
}
注意:此版本反转翻译向量以符合 - 似乎是 - 标准。