计算相机位置以在屏幕上的特定位置查看给定点

时间:2019-02-04 19:24:08

标签: c++ math 3d projection

我正在尝试解决以下问题:

我在世界空间中有一个球体对象,它的表面坐标被映射到地理坐标。我正在使用透视投影相机查看该表面。作为输入,我在地理坐标中有一个矩形(或由带有topLeft == bottomRight坐标的矩形模拟的点,滚动,俯仰和用户还可以定义一些填充来定义可见视图的区域,这些区域可以被gui元素覆盖。需要计算相机的位置和距离,以便在相机的视图中完全可见地理矩形(点)。如果我从顶部笔直看(没有相机倾斜),这很好,这是一个草图: / p>

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);

首先我计算矩形的对齐尺寸,也就是当我的视图与地理坐标空间对齐时的矩形尺寸,然后计算矩形的尺寸(如果旋转),并在公式中添加填充。接下来,我计算需要将其移动以查看具有所需填充的矩形的摄像机距离:

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

但是我不太确定如何实现这一目标。有什么想法吗?

0 个答案:

没有答案