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

时间:2018-02-28 18:53:05

标签: c# collision-detection physics

我已经按照本教程: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;
    }

2 个答案:

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