我正在尝试解决以下问题:
我在世界空间中有一个球体对象,它的表面坐标被映射到地理坐标。我正在使用透视投影相机查看该表面。作为输入,我在地理坐标中有一个矩形(或由带有topLeft == bottomRight
坐标的矩形模拟的点,滚动,俯仰和用户还可以定义一些填充来定义可见视图的区域,这些区域可以被gui元素覆盖。需要计算相机的位置和距离,以便在相机的视图中完全可见地理矩形(点)。如果我从顶部笔直看(没有相机倾斜),这很好,这是一个草图: / p>
输入地理矩形以绿色显示,并已旋转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位置和距离,因此给定的地理矩形(点)与以前的场景一样位于非填充区域中,但是现在我也定义了摄像机的俯仰角:
但是我不太确定如何实现这一目标。有什么想法吗?