自定义物理引擎中的AABB与Circle冲突

问题描述 投票:0回答:2

我已经按照本教程: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;
    }
c# collision-detection physics
2个回答
2
投票

在对代码逻辑进行详细检查之前,我发现了这个潜在的错误:

result.normal = -normal / d;

由于d被设置为normal.LengthSquared而不是normal.Length应用,所以应用的位置校正可以(更大)小于或(大)大于预期。鉴于你的物体“粘在一起”,很可能是前者,即d > 1

(修复当然只是result.normal = -normal / Math.Sqrt(d);

请注意,上述可能不是唯一的错误来源;如果仍有不良行为,请告诉我。


1
投票

虽然你的标签指定了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 ResolutionLearnOpenGL : 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物理引擎,但只是简单地讨论了碰撞检测,因为它们是完整的书,致力于这种复杂的野兽的日益普及。

© www.soinside.com 2019 - 2024. All rights reserved.