我已经按照本教程: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个collison分辨率方法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;
}
答案 0 :(得分:2)
在详细检查代码逻辑之前,我发现了这个潜在的错误:
result.normal = -normal / d;
由于d
设置为normal.LengthSquared
而不是normal.Length
,因为应,因此应用的位置修正可能(更大)更小或(更多) )大于预期。鉴于你的对象是“#34;”,它可能是前者,即d > 1
。
(修正当然只是result.normal = -normal / Math.Sqrt(d);
)
请注意,上述内容可能不是唯一的错误来源;让我知道是否还有不良行为。
答案 1 :(得分:1)
虽然您的标签指定了C#;这里是基本的AABB到AABB&使用C ++完成的AABB to Circle冲突来自:LernOpenGL:InPractice:2DGame : Collision Detection
AABB - AABB Collsion
// 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
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 - 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));
}
。
AABB - 与碰撞解决方案的圆碰撞
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
等,其中考虑并继承了{{1}但是算法本身应该提供有用的功能,因为这正是您正在寻找的,但来自不同的语言。现在关于你的实际问题,你似乎使用的不仅仅是基本动作,因为看起来你正在使用某种形式的动力学,可以从你的Resolve()
方法中看出。
执行AABB to Circle Collision with Resolution
的整个伪算法如下:
碰撞:
检查碰撞:带框的球
- 首先获得圆点的中心点
- 计算AABB信息(中心和半范围)
- 在两个中心之间获得差异
- 钳制[-Half-extents,Half-Extents]之间的差异
- 将夹紧的值添加到AABB中心以给出最靠近圆圈的点
- 检索&amp;返回中心圆之间的矢量&amp;最近点AABB&amp;检查长度是否<半径(在本例中为
Collision
)。
- 如果返回True元组(GL_TRUE,VectorDirection(差异),差异))
- 请参阅上面的函数以进行VectorDirection实现。
- Else Return tuple(GL_FALSE,UP,glm :: vec2(0,0))
执行碰撞分辨率(测试碰撞是否为真)
- 提取方向&amp;差异向量
- 水平碰撞的测试方向
- 如果是真正反向水平速度
- 获取穿透量(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物理引擎,但只是简单地讨论了碰撞检测,因为它们是完整的书,致力于这种复杂的野兽日益普及。