AABB разрешение столкновения скользящие стороны
Итак, в настоящее время я изобретаю колесо (и многому учусь), пытаясь сделать простой физический движок для моего игрового движка. Я искал в интернете, пытаясь (и терпя неудачу) исправить мою текущую проблему. Есть много ресурсов по этому вопросу, но ни один из тех, которые я нашел, не применим к моему делу.
проблема вкратце: разрешение столкновения не работает как предполагалось на некоторых углах, когда два прямоугольника сталкивающийся. Как это происходит, зависит от размеров прямоугольников. То, что я ищу, - это "кратчайшее перекрытие" для разрешения столкновения или другое довольно простое решение (я открыт для предложений!). (прокрутите вниз для лучшего объяснения и иллюстрации).
предупреждение: следующий код, вероятно, не очень эффективен...
прежде всего, вот мой цикл физики. Он просто проходит через все игровые объекты и проверки если они столкнутся с другими игровыми объектами. Это не эффективно (n^2 и все такое), но пока это работает.
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;
}
}
}
}
}
}
}
},
вот код для фактического обнаружения столкновений. Это также работает нормально.
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.
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 дней, поэтому я был бы очень благодарен, если бы кто-то мог помочь подтолкнуть меня в правильном направлении!
спасибо :)
-- EDIT:--
скольжения также произойдет, если двигаться только в одном направлении вдоль левой или правой сторона.
-- EDIT 2 РАБОЧИЙ КОД:-- См. мой ответ ниже для примера рабочего кода!
3 ответов
важная логическая ошибка, которую вы сделали, это строка:
if (absoluteNormalizedX > absoluteNormalizedY) {
это работает только если оба существа площади.
рассмотрим почти экстремальный случай для вашего примера X-скольжения: если они почти касаются угла:
хотя диаграмма немного преувеличена, вы можете видеть, что absoluteNormalizedX < absoluteNormalizedY
в этом случае-ваша реализация будет двигаться дальше, чтобы решить вертикальную столкновение вместо ожидаемого горизонтального.
другая ошибка заключается в том, что вы всегда устанавливаете соответствующую составляющую скорости на ноль независимо от того, с какой стороны происходит столкновение: вы должны только обнулить компонент, если он находится в противоположном направлении от нормального столкновения, или вы не сможете отойти от поверхности.
хороший способ преодолеть это - также записать столкнувшуюся грань(Ы), когда вы делаете столкновение обнаружение:
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;
}
}
// ...
}
использование битового флага вместо логического массива для эффективности. Затем функцию resolver можно переписать как:
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;
}
},
обратите внимание, что в отличие от исходного кода, выше также позволяет углы сталкиваться-т. е. для скоростей и положений, которые должны быть разрешены вдоль обеих осей.
проблема может быть в том, что вы исправляете и X
и Y
столкновение на основе той же позиции:
- игрок находится в определенном положении. Давайте проверим столкновение.
- нижний правый угол игрока перекрывает верхний левый угол объекта.
-
X
положение исправлено: игрок перемещается влево. - нижний правый угол игрока перекрывает верхний левый угол объекта.
-
Y
позиция исправлено: игрок перемещается вверх. - конечный результат: игрок перемещается вверх и влево.
вам, вероятно, нужно "получить" позицию игрока снова, между проверками.
МОЙ РАБОЧИЙ КОД
Итак, с некоторой помощью и руководством от удивительного @meowgoesthedog я, наконец, попал на правильный путь и нашел то, что искал. Проблема (как отметил @meowgoesthedog) заключалась в том, что мой код действительно работал только с квадратами. Решение состояло в том, чтобы проверить пересечение сталкивающихся тел и решить на основе кратчайшего пересечения. Примечание: это, вероятно, не будет оптимальным решением, если вам нужна точная физика с малым и быстро движущиеся объекты. код для нахождения глубины пересечения основан на следующем: https://github.com/kg/PlatformerStarterKit/blob/0e2fafb8dbc845279fe4116c37b6f2cdd3e636d6/RectangleExtensions.cs который связан с этим проектом: https://msdn.microsoft.com/en-us/library/dd254916(v=xnagamestudio.31).aspx.
вот мой рабочий код:
мой цикл физики не был сильно изменен, за исключением некоторых лучших имен для некоторых функций.
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.resolveDisplace(entity, target);
break;
}
}
}
}
}
}
}
},
обнаружение столкновений также остается прежним.
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;
},
вот код, который в принципе решает проблему. Комментарии в коде должны объяснить, что это очень хорошо.
getIntersectionDepth: function(entity, target) {
// Calculate current and minimum-non-intersecting distances between centers.
var distanceX = entity.getMidX() - target.getMidX();
var distanceY = entity.getMidY() - target.getMidY();
var minDistanceX = entity.halfDim.width + target.halfDim.width;
var minDistanceY = entity.halfDim.height + target.halfDim.height;
// If we are not intersecting at all, return 0.
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY) {
return {
x: 0,
y: 0,
};
}
// Calculate and return intersection depths.
var depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
var depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return {
x: depthX,
y: depthY,
};
},
вот обновленная функция разрешения. Теперь он принимает глубину пересечения для учета при определении оси столкновения, а затем использует знак глубины пересечения для сталкивающейся оси при определении направления на разрешить.
resolveDisplace: function(entity, target) {
var intersection = Vroom.getIntersectionDepth(entity, target);
if (intersection.x !== 0 && intersection.y !== 0) {
if (Math.abs(intersection.x) < Math.abs(intersection.y)) {
// Collision on the X axis
if (Math.sign(intersection.x) < 0) {
// Collision on entity right
entity.pos.x = target.getLeft() - entity.dim.width;
} else {
// Collision on entity left
entity.pos.x = target.getRight();
}
entity.vel.x = 0;
} else if (Math.abs(intersection.x) > Math.abs(intersection.y)) {
// Collision on the Y axis
if (Math.sign(intersection.y) < 0) {
// Collision on entity bottom
entity.pos.y = target.getTop() - entity.dim.height;
} else {
// Collision on entity top
entity.pos.y = target.getBottom();
}
entity.vel.y = 0;
}
}
},
спасибо всем за помощь!