Долго думал как сделать обработку столкновений камеры с землей. Идея, в принципе, простая: пусти луч вниз от камеры «в пол» и проверяй столкновение. Но тут появляются «подводные камни», которые я жуть как не люблю и всегда в таких случаях теряюсь.
Дело в том, что столкновение надо проверять только с определенным объектом, с землей. А ведь на сцене физических объектов еще штук пять, это и машины с колесами, и бонусы, которые вылетают, выплывают или появляются каким-то иным образом на трассе.
В процессе исследования возможностей физического движка (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; |
Физический движок перед непосредственно столкновением объектов делает проверку на совпадение групп и масок коллизий. Как-то так (C++):
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; } |
Вот и все. Код включает в себя только базовые вещи, поэтому он не скомпилируется в таком виде, но основная идея понятна.
Лично я проверяю луч не вниз, а от машины, за которой наблюдаем в сторону наблюдателя. Причем наблюдателя во время проверки я ставлю на полметра ниже. Если этот луч пересекается с землей, но камера сдвигается вдоль этого луча по направлению к машине. Вот код из рабочего проекта:
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. К сожалению, не знаю его имени. В свое время эта документация очень помогла при создании игры «Пинкод. Получи патент первым!».