You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 03:05:42