6

我正在制作一款使用整个星球作为地图的游戏。我已经使用这种技术对球形行星进行了镶嵌,现在正在添加相机控制。

球体的维度为 1 到 -1,因此球体上的每个点也是一个归一化向量。在任何时候,构成球体的六边形图块之一是“选定”图块。然后,玩家可以使用 d-pad 将选择移动到相邻的图块。他们还可以使用模拟摇杆独立旋转相机

我需要对选定的瓷砖和相机做两件事。首先,我需要能够将选择切换到离相机最近的图块。其次,我需要将相机放在突出显示的瓷砖上

球体位于原点,相机位于点 (0,0,1)。图形引擎只允许我围绕 X 和 Y 轴旋转相机,所以为了解决第一个问题,我使用四元数围绕 x 和 y 轴旋转点 (0,0,1) 以找到点相机所在的 3D 空间:

    private Quaternion quat = new Quaternion(0,0,0,0);
    private double[] output = new double[4];
    private double[] cam = new double[3];

    private double camX = 0;
    private double camY = 0;
    private double camZ = 1;


    private double[] getCamPosition(){

        quat.setAxisAngle(1, 0, 0, Math.toRadians(-graphicsEngine.getRotationX()));
        quat.RotateVector(camX, camY, camZ, output);            
        cam[0] = output[0];
        cam[1] = output[1];
        cam[2] = output[2];   

        quat.setAxisAngle(0, 1, 0, Math.toRadians(-graphicsEngine.getRotationY()));
        quat.RotateVector(cam[0], cam[1], cam[2], output);          
        cam[0] = output[0];
        cam[1] = output[1];
        cam[2] = output[2];

        return cam;
    }

然后我比较每个图块的质心与相机位置之间的距离,并取最接近新选择的图块的图块。

但是,为了解决第二个问题,我想做相反的事情。我想取质心(已经是归一化向量的形式),并找出围绕 X 的旋转和围绕 Y 的旋转,以使相机以它为中心

目前,我将相机旋转回(0,0,1),然后在(0,0,1)和质心之间获取X轴和Y轴的角度,并使用它来旋转相机第二次:

private double[] outputArray = new double[2];

/**
 * Cam is always (0,0,1)
 */
public void centreOnSelected(double camX, double camY, double camZ){        

    selectedTile.getCentroidAngles(outputArray);

    outputArray[0] -= Math.atan2(camZ, camY);
    outputArray[1] -= Math.atan2(camX, camZ);

    // this determines if the centroid is pointing away from the camera
    // I.e. is on the far side of the sphere to the camera point (0,0,1)
    if(!selected.getCentroidDirectionY(camX, camZ)){
        outputArray[0] = -Math.PI - outputArray[0];         
    }

    graphicsEngine.rotateCam(Math.toDegrees(outputArray[0]), Math.toDegrees(outputArray[1]));


}

selected(瓷砖类)

void getCentroidAngles(double[] outputArray){
    outputArray[0] = Math.atan2(centroidZ, centroidY);
    outputArray[1] = Math.atan2(centroidX, centroidZ);

}

问题是这不起作用,(x 轴似乎总是不在),我很确定这与获取角度和进行旋转的数学有关

注意:图形引擎首先绕 X 轴旋转,然后绕 Y 轴旋转:

        gl.glRotatef(mRotateX, 1, 0, 0);
        gl.glRotatef(mRotateY, 0, 1, 0);

质心都在正确的位置,并且相机肯定会旋转正确的量,所以我确定问题不在于图形引擎。这也不是将相机重新定位回(0,0,1),因为我已经通过逐步执行程序检查了它的工作原理

我还制作了一个视频来说明这个问题:

http://www.youtube.com/watch?v=Uvka7ifZMlE

这已经困扰了我好几天了,因此非常感谢任何帮助解决此问题!

谢谢詹姆斯

4

1 回答 1

2

不幸的是,我不完全了解这里发生了什么,也无法提供完整的解决方案,但至少可以指出一些需要注意的问题。

我从来没有使用过glRotatef,但是我对这里的转换顺序和符号有点困惑——例如,在给定glRotatef调用的顺序的情况下,你真的先做x旋转吗?

无论如何,这里的问题至少有一部分来自这些方程

outputArray[0] = Math.atan2(centroidZ, centroidY);
outputArray[1] = Math.atan2(centroidX, centroidZ);

首先,你的意思是(centroidY, centroidZ)在第一个?更严重的是,你在这里得到的角度不能用来建立一个旋转(0,0,1)到你的质心,反之亦然。例如,假设您要将质心向量旋转到(0,0,1). 您的 2 个旋转中的每一个都可以单独用于围绕单个轴旋转,将一个组件设置为零。例如,outputArray[0]围绕适当符号的 x 轴旋转会将 y 分量设置为零(假设atan2交换了参数)。或者旋转outputArray[1]关于y轴的正确符号可以将x分量设置为0。但是在首先进行x旋转(例如)将y分量设置为0之后,质心向量会发生变化 - 现在围绕y轴的旋转将设置x组件为 0 不再由outputArray[1].

这些事物的正确公式总是有atan2一个角度和一个acos或另一个角度asin。例如,如果您想要将矢量(1,0,0)带到(x,y,z)您身上的主动旋转的角度,您可以使用

first_angle_around_x  = -asin(y)
second_angle_around_y = atan2(x, z)

继续(x,y,z)(1,0,0)会使用

first_angle_around_x  = atan2(y, z)
second_angle_around_y = -asin(x)

(这些角度描述了当轴“戳你的眼睛”时围绕 x、y 轴的逆时针旋转。)

另一个问题在这里

outputArray[0] -= Math.atan2(camZ, camY);
outputArray[1] -= Math.atan2(camX, camZ);

像这样减去角度仅适用于围绕单个轴的旋转。一旦你从围绕不同轴的旋转构建复合变换,关系就会变得更加复杂——这就是矩阵和四元数派上用场的原因。我猜如果 camX-Z 输入是冗余的,正如方法之前的注释所建议的那样,我猜这段代码可能无关紧要(尽管目前 Z 和 Y 组件在此处的方式会产生非零结果,可能是在我提到的第一个方程式中补偿它们是“错误”的方式;我不确定这是否是故意的)。

将相机移动到所选图块​​的方式还有一个基本问题,尽管它是否真的是一个问题有点主观。因为您只围绕 x 和 y 轴进行旋转,所以您的星球上的每个点都有一个独特的相机方向。问题是没有办法连续选择这样的方向 - 想象一下地球上的一个矢量场,其中每个点的矢量是一个单位矢量,当相机在该点上方时,它指向屏幕 x 方向。根据毛球定理,该场不能连续. 这在实践中意味着在地球上的某个时刻,对相机位置进行看似微小的调整将使行星轮在屏幕上最多旋转 180 度。如果您想避免这种情况,您可以选择基于当前相机位置的方向,以尽量减少旋转量。这确实意味着,例如,如果摄像机以闭环方式移动,那么行星最终可能会在屏幕上“滚动”,而这在您的游戏中可能并不理想。它还要求您的引擎能够在所有 3 个轴上进行旋转。

于 2013-02-17T12:45:37.853 回答