Обработка столкновений (коллизий) камеры с землей в движке Bullet Game Physics

Долго думал как сделать обработку столкновений камеры с землей. Идея, в принципе, простая: пусти луч вниз от камеры «в пол» и проверяй столкновение. Но тут появляются «подводные камни», которые я жуть как не люблю и всегда в таких случаях теряюсь.

Дело в том, что столкновение надо проверять только с определенным объектом, с землей. А ведь на сцене физических объектов еще штук пять, это и машины с колесами, и бонусы, которые вылетают, выплывают или появляются каким-то иным образом на трассе.

В процессе исследования возможностей физического движка (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. К сожалению, не знаю его имени. В свое время эта документация очень помогла при создании игры «Пинкод. Получи патент первым!».

Как научиться шевелить ушами. Теория и практика

Иногда меня просят показать шевеление ушами. Но никто не спрашивает как я это делаю. Расскажу вкратце как это делается. Вы смотрели «Полицейскую академию»? Нет? Ну и дурак зря.

Джордж Гейнс Прошу любить и жаловать, комендант Эрик Лассард (в быту Джордж Гейнс). Именно этот человек в каком-то 199x году заставил меня задуматься, как двигать ушами самому.

В те далекие времена у меня было очень много свободного времени, как у того медвежонка из анекдота. Домашнее задание я практически не делал, потому что прогуливал школу периодически. Ремонт в квартире происходил без моего участия, да и запчасти на машину не надо было искать по всему городу, потому что велосипед. А велосипед «Кама», как известно, хрен сломаешь.

Оказалось, что двигать ушами довольно просто. Многие делают это неосознанно. Видели как у военного или стража порядка шевелится фуражка, когда он удивлен? Ты ему: «Да не было тут никогда одностороннего движения», а он тебе так р-р-аз фуражкой недовольно пошевелит: «Пройдемте, мол, уважаемый, в машину, изымем права». А животное (млекопитающее) шевелит ушами ежедневно помногу раз, особенно если эта скотина сожрала все вкусные конфетки со стола, раскидала мусорное ведро по всей кухне и растрепала твой любимый пуфик.

Чтоб подвигать ушами, надо сильно удивиться, не выражая эмоций лицом. На первых порах лучше использовать фуражку или шапку. Постепенно через многие годы изматывающих тренировок перед зеркалом можно научиться шевелить одно ухо, потом другое затем третье.

Вам кажется это глупостью, но у каждого есть группы мыщц, о которых мы ни сном, ни духом. Это можно ощутить после тренировок в спортзале с опытным тренером. Мыщцы обнаруживаются в самых неожиданных местах, например под ребрами, хотя зачем они там нужны.. Наверно девок на пляже кадрить

А еще можно двигать носом влево вправо и вниз, но об этом в другой раз.

«Толерантность» как много в этом звуке для сердца русского слилось

Челнок пришвартовался к звездолету, и пассажиры расселись по местам, ожидая высадки на планету. Джаммар, стараясь сохранять, как и положено мужчине, равнодушное и непроницаемое выражение лица, прошел по рядам и наконец нашел свое кресло. Его соседкой справа оказалась пожилая женщина.
— В первый раз высаживаетесь на новую планету, молодой человек? — спросила она его, улыбаясь.
Джаммар, раздосадованный тем, что его неопытность так легко заметить, молча кивнул и отвернулся, показывая, что не желает продолжать разговор. «Как не стыдно! — подумал он. — Пожилая женщина, а пользуется косметикой и даже не покрыла голову платком! Как какая-нибудь шлюха!»

Маленький рассказ про правильное применение толерантности и политкорректности: «Уважение культурных традиций» автора Шапиро Максима Анатольевича (а вот его ЖЖ).

Установка оригинальных брызговиков на Nissan Note

Вчера поставил брызговики на машину своими руками. Руководствовался инструкцией в коробке, которая оказалось неправильной для задних брызговиков. И заметкой на форуме Ниссан Клуба (ссылка).

Приступим.. Снимаем заднее колесо, иначе простой отверткой не подобраться.

Перед установкой брызговика снимаем заднее колесо

Выкручиваем три самореза возле колеса и два снизу (один крепит пластиковую заглушку, второй мешает при установке нового). Снимаем заклепки, которые найдем (я нашел две возле колеса и одну снизу). Тщательно моем место крепления, вытираем насухо.

Чистое место крепления брызговиков

Если хорошенько присмотреться к низу, то возле отверстий от двух саморезов можно найти маленькую впадинку. Эту впадинку дырявим отверткой или шилом безжалостно, пока туда не влезет саморез (сверлить брызговик я считаю неправильным, новая вещь все-таки, а бампер уже старый). Ставим заклепки из комплекта задних брызговиков. Не забудьте про нижнюю заклепку, ее надо вставлять туда, где вы проковыряли отверстие шилом. Обратите внимание, что заклепки ставятся не так, как нарисовано в инструкции.

Установленные заклепки на заднем бампере

Затем прижимаем брызговик к месту крепления. Слегка закручиваем три самореза возле колеса и один нижний. После этого, прижимая брызговик, чтобы не было зазоров, закручиваем саморезы окончательно (начиная с верхнего). Вот результат:

Установленный задний брызговик

С передними брызговиками проблем нет вообще. Поворачиваем руль до упора вправо для правого брызговика, влево — для левого. Выкручиваем три шурупа, тряпочкой тщательно отмоем место крепления, насухо вытираем. После этого должно быть вот так:

Вид чистого лонжерона, заляпанного гудроном

По ходу дела, лонжерон у меня заляпан битуумом. Ну и плевать, туда редко кто заглядывает. После промывки и протирки прижимаете брызговик к месту крепления и закручиваете шурупы (следя, чтобы не было щелей между брызговики и кузовом автомобиля). Это настолько банально, что я даже фоткал не стал.

Вот результат всей работы:

Брызговики, вид справа

Итого было потрачено 2.5 часа времени на даче, плюс 2628 деревянных за комплект брызговиков в CTK-Nissan. Самое сложное, с чем пришлось столкнуться — это засунуть домкрат в багажник как положено. :)

Подсказка: домкрат положено в багажник засовывать вверх ногами.

Читайте также про дефлекторы на окна и замену лампочки в заднем «бумеранге».

Bullet Game Physics изменение позиции и ориентации физического тела

Не буду долго размусоливать. Приведу сразу код:

void setTransform( btRigidBody * body, const btVector3 & position, const btQuaternion & rotation )
{
    // Матрица транформации
    btTransform transform;
    transform.setIdentity();
 
    // Заполним матрицу
    transform.setOrigin(position);
    transform.setRotation(rotation);
 
    // Применим матрицу к телу
    body->setWorldTransform(transform);
 
    // Важный этап: обнулим линейную и угловую скорости
    body->setLinearVelocity(btVector3(0, 0, 0));
    body->setAngularVelocity(btVector3(0, 0, 0));
}

Для чего требуется обнуление скоростей? Догадайтесь сами.

Блог Евгения Жирнова