空间查询使您可以几何地检查世界。空间查询包括重叠查询、射线投射(ray-casts)和形状投射(shape-casts)。这些功能让您可以执行以下操作:
有时候,您需要确定某一区域内的所有形状。Box2D 世界使用宽相数据结构提供了一种快速的 log(N) 方法进行重叠测试。Box2D 提供了以下重叠测试:
在考虑特定查询之前,您需要对查询过滤有一个基本的了解。形状与形状的过滤在之前已经讨论过。类似的设置也适用于查询。这使您的查询只考虑某些类别的形状,也可以让您的形状忽略某些查询。
与形状一样,查询本身也可以有一个类别。例如,您可以有一个相机(CAMERA)或投射物(PROJECTILE)类别。
enum MyCategories
{
STATIC = 0x00000001,
PLAYER = 0x00000002,
MONSTER = 0x00000004,
WINDOW = 0x00000008,
CAMERA = 0x00000010,
PROJECTILE = 0x00000020,
};
// 手榴弹与静态世界、怪物和窗口发生碰撞,但不与玩家或其他投射物碰撞。
b2QueryFilter grenadeFilter;
grenadeFilter.categoryBits = PROJECTILE;
grenadeFilter.maskBits = STATIC | MONSTER | WINDOW;
// 视图与静态世界、怪物和玩家发生碰撞。
b2QueryFilter viewFilter;
viewFilter.categoryBits = CAMERA;
viewFilter.maskBits = STATIC | PLAYER | MONSTER;
如果您想查询所有内容,可以使用 b2DefaultQueryFilter()
。
您可以在世界坐标中提供一个 AABB(轴对齐包围盒)和一个 b2OverlapResultFcn()
的实现。世界会调用您的函数,对每个与查询 AABB 重叠的形状执行操作。返回 true
以继续查询,否则返回 false
。例如,以下代码查找所有可能与指定 AABB 相交的形状,并唤醒所有相关的物体。
bool MyOverlapCallback(b2ShapeId shapeId, void* context)
{
b2BodyId bodyId = b2Shape_GetBody(shapeId);
b2Body_SetAwake(bodyId, true);
// 返回 true 以继续查询
return true;
}
// 其他地方...
MyOverlapCallback callback;
b2AABB aabb;
aabb.lowerBound = (b2Vec2){-1.0f, -1.0f};
aabb.upperBound = (b2Vec2){1.0f, 1.0f};
b2QueryFilter filter = b2DefaultQueryFilter();
b2World_OverlapAABB(myWorldId, aabb, filter, MyOverlapCallback, &myGame);
不要对回调顺序做任何假设。形状返回到您的回调函数中的顺序可能看起来是任意的。
AABB 重叠非常快速,但精度不高,因为它只考虑形状的边界框。如果您需要精确的重叠测试,可以使用形状重叠查询。例如,以下是如何获取与查询圆形重叠的所有形状。
b2Circle circle = {b2Vec2_zero, 0.2f};
b2World_OverlapCircle(myWorldId, &circle, b2Transform_identity, grenadeFilter, MyOverlapCallback, &myGame);
您可以使用射线投射进行视线检测、开火等。通过实现 b2CastResultFcn()
回调函数并提供起始点和位移来执行射线投射。世界会调用您的函数,对每个射线击中的形状执行操作。回调函数会提供形状、交点、单位法向量和沿射线的分数距离。您不能对回调函数接收的点的顺序做任何假设。回调可能会先接收到更远的点,再接收到更近的点。
通过返回一个分数值,您可以控制射线投射的继续。返回 0 表示射线投射应终止,返回 1 表示射线投射应继续,仿佛没有发生任何碰撞。如果返回回调函数中的分数值,射线将被裁剪到当前交点。因此,您可以选择对任意形状、所有形状进行射线投射,或通过返回适当的分数对最近的形状进行射线投射。
您还可以返回 -1 以过滤掉该形状,然后射线投射将继续,仿佛该形状不存在。
例如:
// 该结构体捕捉最接近的命中形状
struct MyRayCastContext
{
b2ShapeId shapeId;
b2Vec2 point;
b2Vec2 normal;
float fraction;
};
float MyCastCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context)
{
MyRayCastContext* myContext = context;
myContext->shapeId = shapeId;
myContext->point = point;
myContext->normal = normal;
myContext->fraction = fraction;
return fraction;
}
// 其他地方...
MyRayCastContext context = {0};
b2Vec2 origin = {-1.0f, 0.0f};
b2Vec2 end = {3.0f, 1.0f};
b2Vec2 translation = b2Sub(end, origin);
b2World_CastRay(myWorldId, origin, translation, viewFilter, MyCastCallback, &context);
射线投射结果可能会以任意顺序传递到回调中。这不会影响最近点射线投射的结果(除了在结果相等时)。如果您在沿射线收集多个命中点,可能需要根据命中分数对它们进行排序。有关详细信息,请参阅 RayCastWorld 示例。
形状投射类似于射线投射。您可以将射线投射视为沿一条线上跟踪一个点。形状投射允许您沿一条线跟踪一个形状。由于形状可以具有旋转属性,因此您需要提供一个原点变换,而不是一个原点。
// 该结构体捕捉最接近的命中形状
struct MyRayCastContext
{
b2ShapeId shapeId;
b2Vec2 point;
b2Vec2 normal;
float fraction;
};
float MyCastCallback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context)
{
MyRayCastContext* myContext = context;
myContext->shapeId = shapeId;
myContext->point = point;
myContext->normal = normal;
myContext->fraction = fraction;
return fraction;
}
// 其他地方...
MyRayCastContext context = {0};
b2Circle circle = {b2Vec2_zero, 0.05f};
b2Transform originTransform;
originTransform.p = (b2Vec2){-1.0f, 0.0f};
originTransform.q = b2Rot_identity;
b2Vec2 translation = {10.0f, -5.0f};
b2World_CastCircle(myWorldId, &circle, originTransform, translation, grenadeFilter, MyCastCallback, &context);
形状投射的设置方式与射线投射完全相同。您可以预期形状投射通常会比射线投射慢。因此,只有在射线投射不满足需求时才使用形状投射。
与射线投射一样,形状投射的结果可能会以任意顺序传递到回调中。如果您需要多个已排序的结果,则需要编写一些代码来收集和排序这些结果。