【发布时间】:2023-03-07 04:45:01
【问题描述】:
编辑:感谢 stackoverflow.com 修复 CalculateOverlap() 中缺少某些情况。添加给你看。
我有一个 C# Hitbox 类(参见下面的代码,尤其是 CollisionTest() 方法),它使用 SAT 检测两个 OBB 之间的碰撞。这对于以下情况非常有效...
...但在以下情况下完全失败:
计算出的最小平移向量太高,导致对象跳起来,而不是仅仅调整到碰撞对象。
为了改进我的算法,我需要做什么?非常感谢任何帮助!
using OpenTK;
namespace KWEngine2.Collision3D
{
public class Hitbox
{
private static Vector3[] STATICVERTICES = new Vector3[]
{
new Vector3(-0.5f, -0.5f, +0.5f),
new Vector3(+0.5f, -0.5f, +0.5f),
new Vector3(+0.5f, -0.5f, -0.5f),
new Vector3(-0.5f, -0.5f, -0.5f),
new Vector3(+0.5f, +0.5f, +0.5f),
new Vector3(-0.5f, +0.5f, +0.5f),
new Vector3(-0.5f, +0.5f, -0.5f),
new Vector3(+0.5f, +0.5f, -0.5f)
};
private static Vector3[] STATICNORMALS = new Vector3[]
{
new Vector3(1, 0, 0),
new Vector3(0, 1, 0),
new Vector3(0, 0, 1)
};
private static Vector3 STATICCENTER = new Vector3(0, 0, 0);
// Holds the currently transformed vertices of the bounding box
private Vector3[] _vertices = new Vector3[8];
// Holds the currently transformed surface normals of the bounding box
private Vector3[] _normals = new Vector3[3];
// Holds the currently transformed center point of the bounding box
private Vector3 _center = new Vector3(0, 0, 0);
private static Matrix4 CreateModelMatrix(Vector3 position, Quaternion rotation, Vector3 scale)
{
// Create the basic 4x4 rotation matrix:
Matrix4 m = Matrix4.CreateFromQuaternion(rotation);
// Multiply the first three rows by the scale to add the given scale:
m.Row0 *= scale.X;
m.Row1 *= scale.Y;
m.Row2 *= scale.Z;
// Replace the lowest row with the position data:
m.Row3.X = position.X;
m.Row3.Y = position.Y;
m.Row3.Z = position.Z;
m.Row3.W = 1.0f;
return m;
}
public Hitbox()
{
Update(Vector3.Zero, Quaternion.Identity, Vector3.One);
}
public Hitbox(Vector3 position, Quaternion rotation, Vector3 scale)
{
Update(position, rotation, scale);
}
public void Update(Vector3 position, Quaternion rotation, Vector3 scale)
{
Matrix4 modelMatrix = CreateModelMatrix(position, rotation, scale);
for (int i = 0; i < 8; i++)
{
if (i < 3)
{
Vector3.TransformNormal(ref STATICNORMALS[i], ref modelMatrix, out _normals[i]);
}
// transform the corner points by multiplying them by the model matrix:
Vector3.TransformPosition(ref STATICVERTICES[i], ref modelMatrix, out _vertices[i]);
}
// transform the center point by multiplying it by the model matrix:
Vector3.TransformPosition(ref STATICCENTER, ref modelMatrix, out _center);
}
public static bool CollisionTest(Hitbox a, Hitbox b, out Vector3 MTV)
{
float mtvDistance = float.MaxValue;
float mtvDirection = 1;
MTV = Vector3.Zero;
for (int i = 0; i < 3; i++)
{
bool error;
float shape1Min, shape1Max, shape2Min, shape2Max;
SatTest(ref a._normals[i], ref a._vertices, out shape1Min, out shape1Max);
SatTest(ref a._normals[i], ref b._vertices, out shape2Min, out shape2Max);
if (!Overlaps(shape1Min, shape1Max, shape2Min, shape2Max))
{
return false;
}
else
{
CalculateOverlap(ref a._normals[i], ref shape1Min, ref shape1Max, ref shape2Min, ref shape2Max,
out error, ref mtvDistance, ref MTV, ref mtvDirection, ref a._center, ref b._center);
if (error)
return false;
}
SatTest(ref b._normals[i], ref a._vertices, out shape1Min, out shape1Max);
SatTest(ref b._normals[i], ref b._vertices, out shape2Min, out shape2Max);
if (!Overlaps(shape1Min, shape1Max, shape2Min, shape2Max))
{
return false;
}
else
{
CalculateOverlap(ref b._normals[i], ref shape1Min, ref shape1Max, ref shape2Min, ref shape2Max,
out error, ref mtvDistance, ref MTV, ref mtvDirection, ref a._center, ref b._center);
if (error)
return false;
}
}
return true;
}
private static void SatTest(ref Vector3 axisToTest, ref Vector3[] points, out float minAlong, out float maxAlong)
{
minAlong = float.MaxValue;
maxAlong = float.MinValue;
for (int i = 0; i < points.Length; i++)
{
float dotVal = Vector3.Dot(points[i], axisToTest);
if (dotVal < minAlong) minAlong = dotVal;
if (dotVal > maxAlong) maxAlong = dotVal;
}
}
private static bool Overlaps(float min1, float max1, float min2, float max2)
{
return IsBetweenOrdered(min2, min1, max1) || IsBetweenOrdered(min1, min2, max2);
}
private static bool IsBetweenOrdered(float val, float lowerBound, float upperBound)
{
return lowerBound <= val && val <= upperBound;
}
private static void CalculateOverlap(ref Vector3 axis, ref float shape1Min, ref float shape1Max, ref float shape2Min, ref float shape2Max,
out bool error, ref float mtvDistance, ref Vector3 mtv, ref float mtvDirection, ref Vector3 posA, ref Vector3 posB)
{
// THIS PART IS NEW!
float intersectionDepthScaled;
if (shape1Min < shape2Min)
{
if (shape1Max > shape2Max)
{
float diff1 = shape1Max - shape2Max;
float diff2 = shape2Min - shape1Min;
if(diff1 > diff2)
{
intersectionDepthScaled = shape2Max - shape1Min;
}
else
{
intersectionDepthScaled = shape2Min - shape1Max;
}
}
else
{
intersectionDepthScaled = shape1Max - shape2Min; // default
}
}
else
{
if(shape1Max < shape2Max)
{
float diff1 = shape2Max - shape1Max;
float diff2 = shape1Min - shape2Min;
if (diff1 > diff2)
{
intersectionDepthScaled = shape1Max - shape2Min;
}
else
{
intersectionDepthScaled = shape1Min - shape2Max;
}
}
else
{
intersectionDepthScaled = shape1Min - shape2Max; // default
}
}
// END OF NEW PART
float axisLengthSquared = Vector3.Dot(axis, axis);
if (axisLengthSquared < 1.0e-8f)
{
error = true;
return;
}
float intersectionDepthSquared = (intersectionDepthScaled * intersectionDepthScaled) / axisLengthSquared;
error = false;
if (intersectionDepthSquared < mtvDistance || mtvDistance < 0)
{
mtvDistance = intersectionDepthSquared;
mtv = axis * (intersectionDepthScaled / axisLengthSquared);
float notSameDirection = Vector3.Dot(posA - posB, mtv);
mtvDirection = notSameDirection < 0 ? -1.0f : 1.0f;
mtv = mtv * mtvDirection;
}
}
}
}
【问题讨论】:
-
似乎某处(没有找到在哪里)检查 A 的下限是否在 B 内。您错过了 A 的下限低于 B 的下限和 A 的上限的情况高于 B 的上限。
-
嗯,但是如果我错过了这种情况,算法根本不会检测到碰撞,对吧?但确实如此,只有 MTV 不正确。
-
但是等等,也许在 CalculateOverlap():float intersectionDepthScaled = (shape1Min
-
@Emanuele 请在此处留下答案,以便我将其投票为正确。我已经在上面的代码中修复了它。
标签: c# opengl collision-detection opentk bounding-box