基于Java实现从3D坐标计算屏幕空间位置
问题概述
我正尝试计算游戏中传送点(waypoint)的屏幕空间位置,但无法推导实现该功能所需的数学逻辑。
当前代码
private Vector2f calc(float partialTicks) { // Getting the gui scale adjusted width and height Window window = _minecraft.getWindow(); int width = window.getGuiScaledWidth(); int height = window.getGuiScaledHeight(); // Calculating the center of the screen double screenCenterX = width / 2.0; double screenCenterY = height / 2.0; // Getting the player from minecraft LocalPlayer player = _minecraft.player; assert player != null; // The players looking angle float pitch = player.getXRot(); float yaw = player.getYRot(); // The players field of view double fov = Math.toRadians(player.getFieldOfViewModifier() * 70f); float pitchRadians = (float) Math.toRadians(pitch); float yawRadians = (float) Math.toRadians(yaw); double verticalPixelPerRad = screenCenterY / fov; double horizontalPixelPerRad = screenCenterX / fov; // The players position double px = player.getX(); double py = player.getY(); double pz = player.getZ(); // The warps position double wx = _warp.x(); double wy = _warp.y(); double wz = _warp.z(); // Getting the delta between the players position and the position of the warp double dx = wx - px; double dy = wy - py; double dz = wz - pz; // Calculate the distance between the player and the warp double distance = Math.sqrt(dx * dx + dy * dy + dz * dz); // TODO: Calculate the screen space position of the warp point relative to the players position and rotation return new Vector2f(0,0); }
解决方案
要计算传送点的屏幕位置,核心是把3D世界坐标转换为玩家视角下的局部坐标,再推导视角偏角,最后映射到屏幕像素。以下是完整的实现逻辑:
步骤1:转换到玩家局部坐标系
将传送点的相对位置,按照玩家的**Yaw(左右转向)和Pitch(上下抬头)**旋转到玩家自身的坐标系中:
// 基于Yaw旋转,得到水平方向的局部坐标 double localX = dx * Math.cos(yawRadians) + dz * Math.sin(yawRadians); double localZ = -dx * Math.sin(yawRadians) + dz * Math.cos(yawRadians); double localY = dy; // 基于Pitch旋转,得到垂直方向的局部坐标 double localYRotated = localY * Math.cos(pitchRadians) - localZ * Math.sin(pitchRadians); double localZRotated = localY * Math.sin(pitchRadians) + localZ * Math.cos(pitchRadians);
步骤2:判断传送点是否在视野前方
如果localZRotated <= 0,说明传送点在玩家背后,直接返回屏幕外的位置(可根据需求调整渲染逻辑):
if (localZRotated <= 0) { return new Vector2f(-100, -100); // 返回屏幕外位置,避免渲染 }
步骤3:计算视角偏角
分别算出传送点相对于玩家当前视线的水平偏角和垂直偏角:
// 水平偏角:左右方向的偏移弧度 double horizontalAngle = Math.atan2(localX, localZRotated); // 垂直偏角:上下方向的偏移弧度 double verticalAngle = Math.atan2(localYRotated, Math.sqrt(localX*localX + localZRotated*localZRotated));
步骤4:映射到屏幕像素位置
用之前计算的像素每弧度比值,将偏角转换为屏幕偏移,再叠加屏幕中心坐标得到最终位置:
// 计算屏幕坐标 double screenX = screenCenterX + horizontalAngle * horizontalPixelPerRad; double screenY = screenCenterY - verticalAngle * verticalPixelPerRad; // 可选:限制坐标在屏幕范围内,避免渲染到屏幕外 screenX = Math.max(0, Math.min(width, screenX)); screenY = Math.max(0, Math.min(height, screenY)); return new Vector2f((float) screenX, (float) screenY);
整合后的完整代码
将上述逻辑替换原方法中的TODO部分即可:
private Vector2f calc(float partialTicks) { // Getting the gui scale adjusted width and height Window window = _minecraft.getWindow(); int width = window.getGuiScaledWidth(); int height = window.getGuiScaledHeight(); // Calculating the center of the screen double screenCenterX = width / 2.0; double screenCenterY = height / 2.0; // Getting the player from minecraft LocalPlayer player = _minecraft.player; assert player != null; // The players looking angle float pitch = player.getXRot(); float yaw = player.getYRot(); // The players field of view double fov = Math.toRadians(player.getFieldOfViewModifier() * 70f); float pitchRadians = (float) Math.toRadians(pitch); float yawRadians = (float) Math.toRadians(yaw); double verticalPixelPerRad = screenCenterY / fov; double horizontalPixelPerRad = screenCenterX / fov; // The players position double px = player.getX(); double py = player.getY(); double pz = player.getZ(); // The warps position double wx = _warp.x(); double wy = _warp.y(); double wz = _warp.z(); // Getting the delta between the players position and the position of the warp double dx = wx - px; double dy = wy - py; double dz = wz - pz; // Calculate the distance between the player and the warp double distance = Math.sqrt(dx * dx + dy * dy + dz * dz); // 转换到玩家局部坐标系 double localX = dx * Math.cos(yawRadians) + dz * Math.sin(yawRadians); double localZ = -dx * Math.sin(yawRadians) + dz * Math.cos(yawRadians); double localY = dy; // 考虑Pitch旋转后的坐标 double localYRotated = localY * Math.cos(pitchRadians) - localZ * Math.sin(pitchRadians); double localZRotated = localY * Math.sin(pitchRadians) + localZ * Math.cos(pitchRadians); // 判断是否在视野前方 if (localZRotated <= 0) { return new Vector2f(-100, -100); // 返回屏幕外位置 } // 计算视角偏角 double horizontalAngle = Math.atan2(localX, localZRotated); double verticalAngle = Math.atan2(localYRotated, Math.sqrt(localX*localX + localZRotated*localZRotated)); // 映射到屏幕坐标 double screenX = screenCenterX + horizontalAngle * horizontalPixelPerRad; double screenY = screenCenterY - verticalAngle * verticalPixelPerRad; // 可选:限制在屏幕范围内 screenX = Math.max(0, Math.min(width, screenX)); screenY = Math.max(0, Math.min(height, screenY)); return new Vector2f((float) screenX, (float) screenY); }
内容的提问来源于stack exchange,提问作者Drew Chase
相关产品推荐
相关产品推荐

