Долго думал как сделать обработку столкновений камеры с землей. Идея, в принципе, простая: пусти луч вниз от камеры «в пол» и проверяй столкновение. Но тут появляются «подводные камни», которые я жуть как не люблю и всегда в таких случаях теряюсь.
Дело в том, что столкновение надо проверять только с определенным объектом, с землей. А ведь на сцене физических объектов еще штук пять, это и машины с колесами, и бонусы, которые вылетают, выплывают или появляются каким-то иным образом на трассе.
В процессе исследования возможностей физического движка (Bullet Game Physics), применяя опыт работы с Box2D, с ходу нашел один способ: через btCollisionObject::setUserPointer(void*). Это такой метод у каждого физического объекта, который позволяет установить ссылку на свои данные. Можно всем объектам назначить имена с помощью этого метода, а затем по имени проверять «Ground» это или не очень. Или структуру хитрую туда воткнуть, в общем полет фантазии не ограничен.
Но затем я нашел способ лучше — через группы коллизий. Каждому физическому объекту вы назначаете группу и битовую маску. Вот как это выглядит (псевдокод):
object a, b;
a.collision_grp = 00000001b;
a.collision_msk = 00000011b;
b.collision_grp = 00000010b;
b.collision_msk = 00000011b; |
object a, b;
a.collision_grp = 00000001b;
a.collision_msk = 00000011b;
b.collision_grp = 00000010b;
b.collision_msk = 00000011b;
Физический движок перед непосредственно столкновением объектов делает проверку на совпадение групп и масок коллизий. Как-то так (C++):
bool needsCollision( const object & a, const object & b )
{
return (a.collision_grp & b.collision_msk)
&& (a.collision_msk & b.collision_grp);
} |
bool needsCollision( const object & a, const object & b )
{
return (a.collision_grp & b.collision_msk)
&& (a.collision_msk & b.collision_grp);
}
Исходя из этих знаний, проверка столкновения камеры и земли выглядит тривиально, главное правильно расставить битовые маски и группы.
enum CollisionGroup
{
CG_GROUND = 0x01;
CG_CAMERA = 0x02;
};
enum CollisionMask
{
CM_GROUND = CG_CAMERA | CG_GROUND,
CM_CAMERA = CG_GROUND
};
btRigidBody * gGroundBody = NULL;
// Создадим землю с правильной группой и маской (псевдокод)
void createGround()
{
gGroundBody =
new GroundBody(collisionGroup=CG_GROUND, collisionMask=CM_GROUND);
}
bool checkCameraCollision( const btVector3 & p )
{
// Начальная точка луча (непосредственно камера)
btVector3 rayFromWorld = p;
// Направление луча вниз
btVector3 rayToWorld = btVector3(p.x, p.y - 0.5, p.z);
// Структура для получения рез-та коллизий луча
btCollisionWorld::ClosestRayResultCallback
callback(rayFromWorld, rayToWorld);
// Луч может сталкиваться только с землей
callback.m_collisionFilterMask = CM_CAMERA;
// Луч является камерой
callback.m_collisionFilterGroup = CG_CAMERA;
// Пульнем луч
world->rayTest(rayFromWorld, rayToWorld, callback);
// Проверим результат
if (callback.hasHit())
{
// Вот тут живет координата пересечения луча с землей
// если кому надо: callback.m_hitPointWorld
return true;
}
return false;
} |
enum CollisionGroup
{
CG_GROUND = 0x01;
CG_CAMERA = 0x02;
};
enum CollisionMask
{
CM_GROUND = CG_CAMERA | CG_GROUND,
CM_CAMERA = CG_GROUND
};
btRigidBody * gGroundBody = NULL;
// Создадим землю с правильной группой и маской (псевдокод)
void createGround()
{
gGroundBody =
new GroundBody(collisionGroup=CG_GROUND, collisionMask=CM_GROUND);
}
bool checkCameraCollision( const btVector3 & p )
{
// Начальная точка луча (непосредственно камера)
btVector3 rayFromWorld = p;
// Направление луча вниз
btVector3 rayToWorld = btVector3(p.x, p.y - 0.5, p.z);
// Структура для получения рез-та коллизий луча
btCollisionWorld::ClosestRayResultCallback
callback(rayFromWorld, rayToWorld);
// Луч может сталкиваться только с землей
callback.m_collisionFilterMask = CM_CAMERA;
// Луч является камерой
callback.m_collisionFilterGroup = CG_CAMERA;
// Пульнем луч
world->rayTest(rayFromWorld, rayToWorld, callback);
// Проверим результат
if (callback.hasHit())
{
// Вот тут живет координата пересечения луча с землей
// если кому надо: callback.m_hitPointWorld
return true;
}
return false;
}
Вот и все. Код включает в себя только базовые вещи, поэтому он не скомпилируется в таком виде, но основная идея понятна.
Лично я проверяю луч не вниз, а от машины, за которой наблюдаем в сторону наблюдателя. Причем наблюдателя во время проверки я ставлю на полметра ниже. Если этот луч пересекается с землей, но камера сдвигается вдоль этого луча по направлению к машине. Вот код из рабочего проекта:
btVector3 CameraManager::fixCameraPos( const btVector3 & cameraPos,
const btVector3 & targetPos ) const
{
btVector3 rayFromWorld = targetPos;
btVector3 rayToWorld = cameraPos btVector3(0, 0.5, 0);
btCollisionWorld::ClosestRayResultCallback
callback(rayFromWorld, rayToWorld);
callback.m_collisionFilterMask = CM_CAMERA;
callback.m_collisionFilterGroup = CG_GROUND;
btDynamicsWorld * world =
Engine::getSingleton().getWorld()->getBulletDynamicsWorld();
world->rayTest(rayFromWorld, rayToWorld, callback);
if (callback.hasHit())
{
return btVector3(0, 0.5, 0) + callback.m_hitPointWorld;
}
return cameraPos;
} |
btVector3 CameraManager::fixCameraPos( const btVector3 & cameraPos,
const btVector3 & targetPos ) const
{
btVector3 rayFromWorld = targetPos;
btVector3 rayToWorld = cameraPos btVector3(0, 0.5, 0);
btCollisionWorld::ClosestRayResultCallback
callback(rayFromWorld, rayToWorld);
callback.m_collisionFilterMask = CM_CAMERA;
callback.m_collisionFilterGroup = CG_GROUND;
btDynamicsWorld * world =
Engine::getSingleton().getWorld()->getBulletDynamicsWorld();
world->rayTest(rayFromWorld, rayToWorld, callback);
if (callback.hasHit())
{
return btVector3(0, 0.5, 0) + callback.m_hitPointWorld;
}
return cameraPos;
}
UPD: написал продолжение темы коллизий (ссылка)
P.S. И кстати, огромное спасибо автору русского перевода документации к Box2D. К сожалению, не знаю его имени. В свое время эта документация очень помогла при создании игры «Пинкод. Получи патент первым!».