Вычислить положение камеры, чтобы увидеть заданную точку в определенном месте на экране - PullRequest
0 голосов
/ 04 февраля 2019

Я пытаюсь решить следующую проблему:

У меня есть объект сферы в мировом пространстве, и его координаты поверхности сопоставлены с геокоординатами.Я использую перспективную проекционную камеру, чтобы смотреть на эту поверхность.В качестве входных данных у меня есть прямоугольник в географических координатах (или точка, имитируемая этим прямоугольником с topLeft == bottomRight координаты, крен, высота тона и пользователь, также может определить некоторые отступы для определения областей видимого вида, которые могут быть покрыты элементами графического интерфейса. Мне нужновычислите положение и расстояние камеры, чтобы гео-прямоугольник (точка) был полностью виден в поле зрения камеры. Это нормально, если я смотрю на поверхность прямо сверху (без наклона камеры). Вот эскиз:

Top down view

Входной гео-прямоугольник показан зеленым цветом и повернут на 45 градусов. Также имеются отступы в измерениях x и y, охватывающие 0,5f пространства экрана в обоих направлениях.Вот как я вычисляю свойства камеры:

void ComputeParams(const GeoRect& rectangle, const Point2& screenCenter, float rotation, const Margin& padding)
{
    float fFovX = 0.0f, fFovY = 0.0f;
    camera.GetCamera().GetFOV().GetFovXY(fFovX, fFovY);

    ASSERT(!FLOAT_EQUAL(padding.left + padding.right, 1.0f, 0.01f));
    ASSERT(!FLOAT_EQUAL(padding.top + padding.bottom, 1.0f, 0.01f));

    // Clamp padding to < 1.0f to prevent division by 0 later
    const float horizontalPadding = std::clamp(padding.left + padding.right, 0.0f, 0.99f);
    const float verticalPadding = std::clamp(padding.top + padding.bottom, 0.0f, 0.99f);

    const float angle = DegToRad(rotation);
    const float sinAngle = Math::Sin(angle);
    const float cosAngle = Math::Cos(angle);
    const float tanHalfFovX = Math::Tan(fFovX * 0.5f);
    const float tanHalfFovY = Math::Tan(fFovY * 0.5f);

    const auto correctionX = Math::Cos(DegToRad(rectangle.GetCenter().lon / 100000.f));
    const auto alignedWidth = rectangle.GetWidth() * correctionX;
    const auto alignedHeight = rectangle.GetHeight();

    float rotatedWidth = alignedHeight * std::abs(sinAngle) + alignedWidth * std::abs(cosAngle);
    float rotatedHeight = alignedWidth * std::abs(sinAngle) + alignedHeight * std::abs(cosAngle);
    rotatedWidth /= (1.0f - horizontalPadding);
    rotatedHeight /= (1.0f - verticalPadding);

Сначала я вычисляю выровненный размер прямоугольника, он же размер, когда мой вид выровнен с пространством гео координат, затем я вычисляю размер прямоугольника, если он повернут итакже добавление отступа к уравнению. Затем я вычисляю расстояние камеры, где мне нужно переместить его, чтобы увидеть прямоугольник с нужным падди.ng:

const float cameraDistX = (rotatedWidth * 0.5f) / tanHalfFovX;  // Distance to fit rotated rectangle in view frustum in X dimension
    const float cameraDistY = (rotatedHeight * 0.5f) / tanHalfFovY; // Distance to fit rotated rectangle in view frustum in Y dimension

    // Map computed camera distance to allowed range
    const auto distanceRange = GetMinMaxDistance();
    const auto distance = std::clamp(std::max(cameraDistX, cameraDistY), distanceRange.min, distanceRange.max);

    float newWidth{ rotatedWidth }, newHeight{ rotatedHeight };
    if (cameraDistY < distance)
    {
        newHeight = 2.f * distance * tanHalfFovY;
    }

    if (cameraDistX < distance)
    {
        newWidth = 2.f * distance * tanHalfFovX;
    }

Затем я вычисляю положение lookAt, чтобы прямоугольник располагался в запрашиваемой части экрана, я также использую значение screenCenter, которое в основном является точкой фокусировки камеры, вокруг которойон вращается, но давайте представим, что это всегда 0.5f, 0.5f (середина экрана):

const float newPaddingHorizontal = newWidth * (-padding.left + padding.right);
    const float horizontalShift = ((newWidth - newPaddingHorizontal) * 0.5f - newWidth * 0.5f);

    const float newPaddingVertical = newHeight * (-padding.bottom + padding.top);
    const float verticalShift = ((newHeight - newPaddingVertical) * 0.5f - newHeight * 0.5f);

    // pitch, roll, yaw 
    Point3 r(-MATH_HALFPI, angle, 0.0f);
    auto lookAt = rectangle.GetCenter();

    Matrix4 rotationMatrix;
    rotationMatrix.SetIdentity();
    rotationMatrix.Rotate(r);

    auto vMoveX = rotationMatrix.GetAxis(0);
    vMoveX *= (horizontalShift + newWidth * (0.5f - screenCenter.x));
    vMoveX.x /= correctionX;

    auto vMoveY = rotationMatrix.GetAxis(1);
    vMoveY *= (verticalShift + newHeight * (0.5f - screenCenter.y));
    vMoveY.x /= correctionX;

    const auto vMove = vMoveX + vMoveY;

    lookAt.lat = lookAt.lat - (int32_t)vMove.x;
    lookAt.lon = lookAt.lon + (int32_t)vMove.z;

    return newCameraPosition;

Это все работает хорошо, но теперь мне нужно вычислить положение и расстояние камеры lookAt, поэтому с учетом геоПрямоугольник (точка) находится в области без отступов, как в предыдущем сценарии, но теперь у меня также определен шаг камеры:

enter image description here

, но я не совсемуверен, как этого добиться.Есть идеи?

...