AABB 碰撞解决方案出现问题 [英] AABB collision resolution slipping sides

查看:32
本文介绍了AABB 碰撞解决方案出现问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,我目前正在通过尝试为我的游戏引擎制作一个简单的物理引擎来重新发明轮子(并且学到了很多东西).我一直在搜索互联网,试图(但失败)解决我当前的问题.有很多关于这个主题的资源,但我发现的资源似乎都不适用于我的案例.

So, I am currently reinventing the wheel (and learning a lot) by trying my hand at making a simple physics engine for my game engine. I have been searching the internet, trying (and failing) to fix my current problem. There are a lot of resources out there on the subject, but none of those that I have found seem to apply to my case.

问题简而言之:当两个矩形发生碰撞时,碰撞解决方案在某些角上无法按预期工作.它如何失败取决于矩形的尺寸.我正在寻找的是碰撞的最短重叠"解决方案或其他相当简单的解决方案(我愿意接受建议!).(向下滚动以获得更好的解释和插图).

THE PROBLEM IN SHORT: The collision resolution does not work as intended on some of the corners when two rectangles are colliding. How it fails varies based on the dimensions of the rectangles. What I am looking for is a "shortest overlap" kind of resolution for the collision or another fairly simple solution (I am open for suggestions!). (Scroll down for a better explaination and illustrations).

警告:以下代码可能效率不高...

首先,这是我的物理循环.它只是循环遍历所有游戏实体并检查它们是否与任何其他游戏实体发生冲突.它效率不高(n^2 和所有这些),但目前有效.

First of all, here is my physics loop. It simply loops through all of the game entities and checks if they collide with any other game entities. It is not efficient (n^2 and all of that), but it works for now.

updatePhysics: function(step) {
  // Loop through entities and update positions based on velocities
  for (var entityID in Vroom.entityList) {
    var entity = Vroom.entityList[entityID];
    if (entity.physicsEnabled) {
      switch (entity.entityType) {
        case VroomEntity.KINEMATIC:
          entity.pos.x += entity.vel.x * step;
          entity.pos.y += entity.vel.y * step;
          break;

        case VroomEntity.DYNAMIC:
          // Dynamic stuff
          break;
      }
    }
  }
  // Loop through entities and detect collisions. Resolve collisions as they are detected.
  for (var entityID in Vroom.entityList) {
    var entity = Vroom.entityList[entityID];
    if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
      for (var targetID in Vroom.entityList) {
        if (targetID !== entityID) {
          var target = Vroom.entityList[targetID];
          if (target.physicsEnabled) {
            // Check if current entity and target is colliding
            if (Vroom.collideEntity(entity, target)) {
              switch (entity.collisionType) {
                case VroomEntity.DISPLACE:
                  Vroom.resolveTestTest(entity, target);
                  break;
              }
            }
          }
        }
      }
    }
  }
},

这里是实际碰撞检测的代码.这似乎也可以正常工作.

Here is the code for the actual collision detection. This also seems to work alright.

collideEntity: function(entity, target) {
  if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() ||  entity.getRight() < target.getLeft() ||  entity.getLeft() > target.getRight()) {
    return false;
  }

  return true;
},

这里是问题开始出现的地方.我希望实体简单地被推出"目标实体并将速度设置为 0.只要实体和目标都是完美的正方形,这就可以正常工作.如果假设实体(gif 中的玩家人物)是一个矩形,那么当最长边(X 轴)与目标(正方形)碰撞时,碰撞将滑动".如果我交换播放器尺寸使其短而宽,那么 Y 轴会出现同样的问题.

Here is where the problems start to pop up. I want the entity to simply be "pushed" out of the target entity and have the velocity set to 0. This works fine as long as both the entity and the target are perfect squares. If let's say the entity (the player figure in the gif) is a rectangle, then the collision will "slipp" when colliding the longest sides (the X axis) with the target (the square). If I swap the player dimensions so that it is short and wide, then the same problem appears for the Y axis instead.

resolveTestTest: function(entity, target) {
  var normalizedX = (target.getMidX() - entity.getMidX());
  var normalizedY = (target.getMidY() - entity.getMidY());
  var absoluteNormalizedX = Math.abs(normalizedX);
  var absoluteNormalizedY = Math.abs(normalizedY);

  console.log(absoluteNormalizedX, absoluteNormalizedY);

  // The collision is comming from the left or right
  if (absoluteNormalizedX > absoluteNormalizedY) {
    if (normalizedX < 0) {
      entity.pos.x = target.getRight();
    } else {
      entity.pos.x = target.getLeft() - entity.dim.width;
    }

    // Set velocity to 0
    entity.vel.x = 0;

    // The collision is comming from the top or bottom
  } else {
    if (normalizedY < 0) {
      entity.pos.y = target.getBottom();
    } else {
      entity.pos.y = target.getTop() - entity.dim.height;
    }

    // Set velocity to 0
    entity.vel.y = 0;
  }

},

Y 轴上的碰撞适用于这些形状

X 轴上的碰撞与这些形状滑动

我该怎么做才能解决这个滑倒问题?在过去的 5 天里,我一直在反对这一点,所以如果有人能帮助我朝着正确的方向前进,我将不胜感激!

What can I do to fix this slipping problem? I have been bashing my head against this for the last 5 days, so I would be immensely grateful if some one could help push me in the right direction!

谢谢:)

-- --

如果只沿左侧或右侧的一个方向移动也会发生滑动.

The slipping also happens if only moving in one direction along the left or right side.

-- 编辑 2 工作代码:--有关工作代码的示例,请参阅下面我的回答!

-- EDIT 2 WORKING CODE: -- See my answer below for an example of the working code!

推荐答案

您犯的重要逻辑错误是这一行:

The important logical error you have made is this line:

if (absoluteNormalizedX > absoluteNormalizedY) {

这只适用于两个实体都是正方形的情况.

This only works if both entities are square.

为您的 X 滑移示例考虑一个接近极值的情况:如果它们几乎在拐角处接触:

Consider a near-extremal case for your X-slipping example: if they almost touch at the corner:

虽然图有点夸张,但是可以看到absoluteNormalizedX <在这种情况下,absoluteNormalizedY - 您的实现将继续解决垂直碰撞而不是预期的水平碰撞.

Although the diagram is a little exaggerated, you can see that absoluteNormalizedX < absoluteNormalizedY in this case - your implementation would move on to resolve a vertical collision instead of the expected horizontal one.

另一个错误是,无论碰撞在哪一侧,您始终将相应的速度分量设置为零:如果该分量与碰撞法线方向相反,则必须仅将其归零,否则将无法远离表面.

Another error is that you always set the corresponding velocity component to zero regardless of which side the collision is on: you must only zero the component if is it in the opposite direction to the collision normal, or you won't be able to move away from the surface.

克服这个问题的一个好方法是在进行碰撞检测时记录碰撞的人脸:

A good way to overcome this is to also record the collided face(s) when you do collision detection:

collideEntity: function(entity, target) {
   // adjust this parameter to your liking
   var eps = 1e-3;

   // no collision
   var coll_X = entity.getRight() > target.getLeft() && entity.getLeft() < target.getRight();
   var coll_Y = entity.getBottom() > target.getTop() && entity.getTop() < target.getBottom();
   if (!(coll_X && coll_Y)) return 0;

   // calculate bias flag in each direction
   var bias_X = entity.targetX() < target.getMidX();
   var bias_Y = entity.targetY() < target.getMidY();

   // calculate penetration depths in each direction
   var pen_X = bias_X ? (entity.getRight() - target.getLeft())
                      : (target.getRight() - entity.getLeft());
   var pen_Y = bias_Y ? (entity.getBottom() - target.getUp())
                      : (target.getBottom() - entity.getUp());
   var diff = pen_X - pen_Y;

   // X penetration greater
   if (diff > eps)
      return (1 << (bias_Y ? 0 : 1));

   // Y pentration greater
   else if (diff < -eps) 
      return (1 << (bias_X ? 2 : 3));

   // both penetrations are approximately equal -> treat as corner collision
   else
      return (1 << (bias_Y ? 0 : 1)) | (1 << (bias_X ? 2 : 3));
},

updatePhysics: function(step) {
   // ...
            // pass collision flag to resolver function
            var result = Vroom.collideEntity(entity, target);
            if (result > 0) {
              switch (entity.collisionType) {
                case VroomEntity.DISPLACE:
                  Vroom.resolveTestTest(entity, target, result);
                  break;
              }
            }
   // ...
}

使用位标志代替布尔数组以提高效率.解析器函数可以重写为:

Using a bit flag instead of a boolean array for efficiency. The resolver function can then be re-written as:

resolveTestTest: function(entity, target, flags) {
  if (!!(flags & (1 << 0))) {  // collision with upper surface
      entity.pos.y = target.getTop() - entity.dim.height;
      if (entity.vel.y > 0)  // travelling downwards
         entity.vel.y = 0;
  } 
  else
  if (!!(flags & (1 << 1))) {  // collision with lower surface
      entity.pos.y = target.getBottom();
      if (entity.vel.y < 0)  // travelling upwards
         entity.vel.y = 0;
  }

  if (!!(flags & (1 << 2))) {  // collision with left surface
      entity.pos.x = target.getLeft() - entity.dim.width;
      if (entity.vel.x > 0)  // travelling rightwards
         entity.vel.x = 0;
  } 
  else
  if (!!(flags & (1 << 3))) {  // collision with right surface
      entity.pos.x = target.getRight();
      if (entity.vel.x < 0)  // travelling leftwards
         entity.vel.x = 0;
  }
},

请注意,与您的原始代码不同,上面的代码还允许 拐角 发生碰撞 - 即沿两个轴解析速度和位置.

Note that unlike your original code, the above also allows corners to collide - i.e. for velocities and positions to be resolved along both axes.

这篇关于AABB 碰撞解决方案出现问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆