Ogre + Bullet: проверка столкновения камеры с землей

Хочу предложить более совершенный алгоритм проверки коллизии камеры с землей, чем опубликованный ранее (ссылка). Теперь все как «у взрослых» (так сделано, например, в Perfect World). Задача: камера не должна проходить сквозь стены.

Оговорим обозначения:

  • cameraPos — позиция камеры
  • targetPos — позиция цели (за которой следит камера)
  • leftTop, rightTop, leftBottom, rightBottom — позиции крайних точек экрана в мировых координатах
  • hitPoint — точка коллизии луча с землей

В общих чертах, алгоритм такой:

  • вычисляем координаты leftTop, rightTop, rightBottom, leftBottom
  • для каждой крайней точки p создаем луч, начало которого в targetPos, а окончание в p
  • для каждого луча проверяем столкновение с землей, назовем точку столкновения hitPoint
  • вычисляем минимальную длину проекции вектора (hitPointtargetPos) на вектор (cameraPostargetPos)
  • новая позиция камеры — это targetPos смещенная в направлении cameraPos на минимальную длину проекции

А теперь выразим это в коде (код писался прямо в блокноте, поэтому не обессудьте)

bool checkCameraCollides( const Ogre::Vector3 & cameraPos, 
    const Ogre::Vector3 & targetPos, Ogre::Vector3 & newCameraPos )
{
    // Сохраняем позицию и направление камеры
    const Ogre::Vector3 lastCameraPos = camera->getPosition();
    const Ogre::Vector3 lastCameraDir = camera->getDirection();
 
    // Установим новую позицию и направление камеры для корректной 
    // работы метода Ogre::Camera::getCameraToViewportRay()
    camera->setPosition(cameraPos);
    camera->setDirection(targetPos - cameraPos);
 
    // Вычисляем крайние точки
    std::vector<Ogre::Vector3> points;
    {
        Ogre::Ray ray;
        // Верхний левый угол
        camera->getCameraToViewportRay(0, 0, &ray);
        points.push_back(ray.getOrigin());
        // Нижний левый угол
        camera->getCameraToViewportRay(0, 1, &ray);
        points.push_back(ray.getOrigin());
        // Верхний правый угол
        camera->getCameraToViewportRay(1, 0, &ray);
        points.push_back(ray.getOrigin());
        // Нижний правый угол
        camera->getCameraToViewportRay(1, 1, &ray);
        points.push_back(ray.getOrigin());
    }
 
    // Вернем камере исходные позицию и направление
    camera->setPosition(lastCameraPos);
    camera->setDirection(lastCameraDir);
 
    // Минимальная длина проекции
    float minProj = 0.0f;
    // Есть столкновение
    bool collideDetected = false;
    // Луч от цели до камеры
    const Ogre::Vector3 targetToCameraDir = 
        (cameraPos - targetPos).normalisedCopy();
 
    for (size_t = 0; i < points.size(); ++i)
    {
        // Начало луча в позиции цели
        const btVector3 from = OgreBtConverter::to(targetPos);
        // Окончание луча крайней точке
        const btVector3 to = OgreBtConverter::to(points[i]);
 
        // Настроим стуктуру для обнаружение столкновения
        btDynamicsWorld::ClosestRayResultCallback rayCallback(from, to);
        // Сталкиваться только с землей
        rayCallback.m_collisionFilterGroup = CG_GROUND;
        rayCallback.m_collisionFilterMask = CM_GROUND;
        // Пускаем луч
        dynamicsWorld->rayTest(from, to, rayCallback);
 
        // Есть контакт
        if (rayCallback.hasHit())
        {
            // Точка столкновения
            const Ogre::Vector3 hitPoint = 
                BtOgreConverter::to(rayCallback.m_hitPointWorld);
 
            // Длина проекции на луч от targetPos до cameraPos
            float proj = (hitPoint - targetPos).dotProduct(targetToCameraDir);
 
            // Сохраним минимальную длину проекции
            if (!collideDetected || minProj > proj)
            {
                minProj = proj;
                collideDetected = true;
            }
        }
    }
 
    // Если столкновение произошло
    if (collideDetected)
    {
        // Новая позиция камеры - это targetPos смещенная в сторону cameraPos на minProj
        newCameraPos = targetPos + targetToCameraDir * minProj;
        return true;
    }
 
    return false;
}

И для «самых маленьких» пример использования:

void updateCameraPosition()
{
    Ogre::Vector3 cameraPos, targetPos, newCameraPos;
    // Вычисляем позицию камеры и цели (решение о том, как это делается, 
    // остается за вами, у меня позиция и цель камеры зависит от режима камеры)
    calculateCameraPosition(cameraPos, targetPos);
    // Проверим столкновение
    if (checkCameraCollides(cameraPos, targetPos, newCameraPos))
    {
        camera->setPosition(newCameraPos);
    }
    else
    {
        camera->setPosition(cameraPos);
    }
    camera->setDirection(targetPos - camera->getPosition());
}
Комментариев нет. Будьте первым!
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

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