我已经按照本教程:https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331在c#中创建了一个2d物理引擎(他几乎一直工作错误且不一致的伪c ++)我有Circle vs Circle碰撞和AABB vs AABB碰撞工作正常。但是当尝试AABB vs Circle collison(下图)时,两个刚体刚刚粘在一起并慢慢向一个方向移动毛刺。
如果有人可以帮助我,我会非常感激,因为我花了几天时间仍然不知道导致错误的原因。
如果有人需要我的代码中的更多信息,我很乐意提供它。
public static bool AABBvsCircle(ref Collision result) {
RigidBody AABB = result.a.Shape is AABB ? result.a : result.b;
RigidBody CIRCLE = result.b.Shape is Circle ? result.b : result.a;
Vector2 n = CIRCLE.Position - AABB.Position;
Vector2 closest = n;
float x_extent = ((AABB)AABB.Shape).HalfWidth;
float y_extent = ((AABB)AABB.Shape).HalfHeight;
closest.X = Clamp(-x_extent, x_extent, closest.X);
closest.Y = Clamp(-y_extent, y_extent, closest.Y);
bool inside = false;
if (n == closest) {
inside = true;
if (Abs(n.X) > Abs(n.Y)) {
// Clamp to closest extent
if (closest.X > 0)
closest.X = x_extent;
else
closest.X = -x_extent;
}
// y axis is shorter
else {
// Clamp to closest extent
if (closest.Y > 0)
closest.Y = y_extent;
else
closest.Y = -y_extent;
}
}
Vector2 normal = n - closest;
float d = normal.LengthSquared();
float r = ((Circle)CIRCLE.Shape).Radius;
// Early out of the radius is shorter than distance to closest point and
// Circle not inside the AABB
if (d > (r * r) && !inside)
return false;
// Avoided sqrt until we needed
d = (float)Sqrt(d);
if (inside) {
result.normal = -normal / d;
result.penetration = r - d;
}
else {
result.normal = normal / d;
result.penetration = r - d;
}
return true;
}
在“碰撞”结构中编辑1个碰撞解决方法
public void Resolve() {
Vector2 rv = b.Velocity - a.Velocity;
float velAlongNormal = Vector2.Dot(rv, normal);
if (velAlongNormal > 0)
return;
float e = Min(a.Restitution, b.Restitution);
float j = -(1 + e) * velAlongNormal;
j /= a.InvertedMass + b.InvertedMass;
Vector2 impulse = j * normal;
a.Velocity -= a.InvertedMass * impulse;
b.Velocity += b.InvertedMass * impulse;
const float percent = 0.2f; // usually 20% to 80%
const float slop = 0.01f; // usually 0.01 to 0.1
Vector2 correction = Max(penetration - slop, 0.0f) / (a.InvertedMass + b.InvertedMass) * percent * normal;
if (float.IsNaN(correction.X) || float.IsNaN(correction.Y))
correction = Vector2.Zero;
a.Position -= a.InvertedMass * correction;
b.Position += b.InvertedMass * correction;
}
在对代码逻辑进行详细检查之前,我发现了这个潜在的错误:
result.normal = -normal / d;
由于d
被设置为normal.LengthSquared
而不是normal.Length
应用,所以应用的位置校正可以(更大)小于或(大)大于预期。鉴于你的物体“粘在一起”,很可能是前者,即d > 1
。
(修复当然只是result.normal = -normal / Math.Sqrt(d);
)
请注意,上述可能不是唯一的错误来源;如果仍有不良行为,请告诉我。
虽然你的标签指定了C#;这里是基本的AABB到AABB和AABB到圆形碰撞,用C ++完成,因为它们来自:LernOpenGL:InPractice:2DGame : Collision Detection
AABB - AABB碰撞
// AABB to AABB Collision
GLboolean CheckCollision(GameObject &one, GameObject &two) {
// Collision x-axis?
bool collisionX = one.Position.x + one.Size.x >= two.Position.x &&
two.Position.x + two.Size.x >= one.Position.x;
// Collision y-axis?
bool collisionY = one.Position.y + one.Size.y >= two.Position.y &&
two.Position.y + two.Size.y >= one.Position.y;
// Collision only if on both axes
return collisionX && collisionY;
}
AABB在没有分辨率的情况下绕圈碰撞
// AABB to Circle Collision without Resolution
GLboolean CheckCollision(BallObject &one, GameObject &two) {
// Get center point circle first
glm::vec2 center(one.Position + one.Radius);
// Calculate AABB info (center, half-extents)
glm::vec2 aabb_half_extents(two.Size.x / 2, two.Size.y / 2);
glm::vec2 aabb_center(
two.Position.x + aabb_half_extents.x,
two.Position.y + aabb_half_extents.y
);
// Get difference vector between both centers
glm::vec2 difference = center - aabb_center;
glm::vec2 clamped = glm::clamp(difference, -aabb_half_extents, aabb_half_extents);
// Add clamped value to AABB_center and we get the value of box closest to circle
glm::vec2 closest = aabb_center + clamped;
// Retrieve vector between center circle and closest point AABB and check if length <= radius
difference = closest - center;
return glm::length(difference) < one.Radius;
}
然后在他的在线教程的下一部分中,他展示了如何使用上面找到的方法来执行Collision Resolution
:LearnOpenGL : Collision Resolution
在本节中,他添加了一个枚举,另一个函数和一个std::tuple<>
来优化上述检测系统,同时试图使代码更容易和更清晰地管理和阅读。
enum Direction {
UP,
RIGHT,
DOWN,
LEFT
};
Direction VectorDirection(glm::vec2 target)
{
glm::vec2 compass[] = {
glm::vec2(0.0f, 1.0f), // up
glm::vec2(1.0f, 0.0f), // right
glm::vec2(0.0f, -1.0f), // down
glm::vec2(-1.0f, 0.0f) // left
};
GLfloat max = 0.0f;
GLuint best_match = -1;
for (GLuint i = 0; i < 4; i++)
{
GLfloat dot_product = glm::dot(glm::normalize(target), compass[i]);
if (dot_product > max)
{
max = dot_product;
best_match = i;
}
}
return (Direction)best_match;
}
typedef std::tuple<GLboolean, Direction, glm::vec2> Collision;
然而,CheckCollsion()
的原始AABB to Circle
函数略有变化,通过更改其声明/定义返回Collision
而不是GLboolean
。
AABB - 与碰撞分辨率的圆碰撞
// AABB - Circle Collision with Collision Resolution
Collision CheckCollision(BallObject &one, GameObject &two) {
// Get center point circle first
glm::vec2 center(one.Position + one.Radius);
// Calculate AABB info (center, half-extents)
glm::vec2 aabb_half_extents(two.Size.x / 2, two.Size.y / 2);
glm::vec2 aabb_center(two.Position.x + aabb_half_extents.x, two.Position.y + aabb_half_extents.y);
// Get difference vector between both centers
glm::vec2 difference = center - aabb_center;
glm::vec2 clamped = glm::clamp(difference, -aabb_half_extents, aabb_half_extents);
// Now that we know the the clamped values, add this to AABB_center and we get the value of box closest to circle
glm::vec2 closest = aabb_center + clamped;
// Now retrieve vector between center circle and closest point AABB and check if length < radius
difference = closest - center;
if (glm::length(difference) < one.Radius) // not <= since in that case a collision also occurs when object one exactly touches object two, which they are at the end of each collision resolution stage.
return std::make_tuple(GL_TRUE, VectorDirection(difference), difference);
else
return std::make_tuple(GL_FALSE, UP, glm::vec2(0, 0));
}
如果在此函数中调用上述函数或方法,则在检测到碰撞时执行实际逻辑。
void Game::DoCollisions()
{
for (GameObject &box : this->Levels[this->Level].Bricks)
{
if (!box.Destroyed)
{
Collision collision = CheckCollision(*Ball, box);
if (std::get<0>(collision)) // If collision is true
{
// Destroy block if not solid
if (!box.IsSolid)
box.Destroyed = GL_TRUE;
// Collision resolution
Direction dir = std::get<1>(collision);
glm::vec2 diff_vector = std::get<2>(collision);
if (dir == LEFT || dir == RIGHT) // Horizontal collision
{
Ball->Velocity.x = -Ball->Velocity.x; // Reverse horizontal velocity
// Relocate
GLfloat penetration = Ball->Radius - std::abs(diff_vector.x);
if (dir == LEFT)
Ball->Position.x += penetration; // Move ball to right
else
Ball->Position.x -= penetration; // Move ball to left;
}
else // Vertical collision
{
Ball->Velocity.y = -Ball->Velocity.y; // Reverse vertical velocity
// Relocate
GLfloat penetration = Ball->Radius - std::abs(diff_vector.y);
if (dir == UP)
Ball->Position.y -= penetration; // Move ball bback up
else
Ball->Position.y += penetration; // Move ball back down
}
}
}
}
// Also check collisions for player pad (unless stuck)
Collision result = CheckCollision(*Ball, *Player);
if (!Ball->Stuck && std::get<0>(result))
{
// Check where it hit the board, and change velocity based on where it hit the board
GLfloat centerBoard = Player->Position.x + Player->Size.x / 2;
GLfloat distance = (Ball->Position.x + Ball->Radius) - centerBoard;
GLfloat percentage = distance / (Player->Size.x / 2);
// Then move accordingly
GLfloat strength = 2.0f;
glm::vec2 oldVelocity = Ball->Velocity;
Ball->Velocity.x = INITIAL_BALL_VELOCITY.x * percentage * strength;
//Ball->Velocity.y = -Ball->Velocity.y;
Ball->Velocity = glm::normalize(Ball->Velocity) * glm::length(oldVelocity); // Keep speed consistent over both axes (multiply by length of old velocity, so total strength is not changed)
// Fix sticky paddle
Ball->Velocity.y = -1 * abs(Ball->Velocity.y);
}
}
现在上面的一些代码是GameSpecific
,如Game
类,Ball
类,Player
等,其中这些被考虑并继承自GameObject
,但算法本身应该提供有用的,因为这正是你正在寻找的但是来自不同的语言。现在关于你的实际问题,你似乎使用的不仅仅是基本运动,因为看起来你正在使用某种形式的动力学,可以从你的Resolve()
方法看到。
执行AABB to Circle Collision with Resolution
的整体伪算法如下:
- 碰撞: 检查碰撞:球与框 首先获得圆点的中心点 计算AABB信息(中心和半范围) 在两个中心之间获得差异向量 夹紧[-Half-extents,Half-Extents]之间的区别 将夹紧的值添加到AABB中心以给出最靠近圆圈的点 检索并返回中心圆和最近点AABB之间的向量并检查长度是否为<半径(在本例中为
Collision
)。 如果True Return元组(GL_TRUE,VectorDirection(差异),差异)) 请参阅上面的函数以实现VectorDirection实现。 否则返回元组(GL_FALSE,UP,glm :: vec2(0,0)) 执行碰撞分辨率(测试碰撞是否为真) 提取方向和差异向量 水平碰撞的试验方向 如果是真正的反向水平速度 获取穿透量(Ball Radius - abs(diff_vector.x)) 测试方向是左还是右(W,E) 如果左 - 将球向右移动(ball.position.x + =穿透) Else Right - 将球向左移动(ball.position.x - =穿透) 垂直碰撞的其他测试方向 如果是真正的反向垂直速度 获得穿透量(Ball Radius - abs(diff_vector.y)) 测试方向是向上还是向下(N,S) 如果向上 - 向上移动球(ball.position.y - =穿透) Else Down - 向下移动球(ball.position.y + =穿透)
现在所示的算法假设boxes
没有旋转,它们的顶部和底部边缘与水平面平行,并且它们的边与窗口屏幕坐标的左右边缘平行。同样在垂直位移的底部,这也假设top left corner of the screen
- the first pixel
是(0,0)
,因此垂直位移的相反操作。这也假设2D
碰撞而不是3D Ridged or Ragdoll
类型碰撞。您可以使用它来比较您自己的源 - 实现,但是只要查看您的代码而不通过调试器运行它,我就很难看到或找出实际导致您的错误的内容。我希望这能为您提供所需的帮助。
上面提到的OpenGL教程网站的代码确实有效,因为我自己测试过。这个算法是最简单的碰撞检测,到目前为止还没有一个全面的系统,它仍然有这里没有提到的警告或陷阱,但它足以满足它所用的应用。如果你需要更多关于碰撞检测的信息,那么可以在Ian Millington's
书中阅读的几章Game Physics Engine Development
虽然他的书是基于广义的3D物理引擎,但只是简单地讨论了碰撞检测,因为它们是完整的书,致力于这种复杂的野兽的日益普及。