假设北为零弧度,如何在极坐标和笛卡尔坐标之间进行转换?

How do you convert between polar coordinates and cartesian coordinates assuming north is zero radians?

我一直在尝试为游戏制作一个简单的物理引擎。我很清楚这是在重新发明轮子,但它更像是一种学习练习,而不是其他任何东西。例如,我从没想过它会像 box2d 一样完整。

我在执行二维向量时遇到问题。这个问题与以下事实有关:在游戏世界中,我想将北表示为零弧度,将东表示为 1/2 PI 弧度,或分别为 0 度和 90 度。然而在数学中(或者更具体地说 Java 的数学 class),我相信像正弦和余弦这样的三角函数假设 "east" 是零弧度,我 认为 北是 1/2 PI 弧度?

无论如何,我已经编写了我的向量的一个小版本 class,它只演示了错误的代码。

public class Vector {
    private final double x;
    private final double y;

    public Vector(double xIn, double yIn) {
        x = xIn;
        y = yIn;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public double getR() {
        return Math.sqrt((x * x) + (y * y));
    }

    public double getTheta() {
        return Math.atan(y / x);
    }

    public double bearingTo(Vector target) {
        return (Math.atan2(target.getY() - y, target.getX() - x));
    }

    public static Vector fromPolar(double magnitude, double angle) {
        return new Vector(magnitude * Math.cos(angle),
                          magnitude * Math.sin(angle));
    }
}

这里是演示问题的测试代码:

public class SOQuestion {
public static void main(String[] args) {
    //This works just fine
    Vector origin = new Vector(0, 0);
    Vector target = new Vector(10, 10);

    double expected = Math.PI * 0.25;
    double actual = origin.bearingTo(target);

    System.out.println("Expected: " + expected);
    System.out.println("Actual: " + actual);

    //This doesn't work
    origin = new Vector(0, 0);
    target = new Vector(10, 0);

    expected = Math.PI * 0.5; //90 degrees, or east.
    actual = origin.bearingTo(target); //Of course this is going to be zero, because in mathematics right is zero

    System.out.println("Expected: " + expected);
    System.out.println("Actual: " + actual);


    //This doesn't work either
    Vector secondTest = Vector.fromPolar(100, Math.PI * 0.5); // set the vector to the cartesian coordinates of (100,0)
    System.out.println("X: " + secondTest.getX()); //X ends up being basically zero
    System.out.println("Y: " + secondTest.getY()); //Y ends up being 100
} }

要求是:

  1. fromPolar(magnitude,angle) 应 return 具有 xy 的矢量初始化为适当的值,假设北为零弧度且东为1/2 PI 弧度。例如 fromPolar(10,PI) 应该构造一个 x: 0 和 y: -1

  2. 的向量
  3. getTheta() 应该 return 一个大于或等于零且小于 2 PI 的值。 Theta 是它所调用的向量的 angular 分量。例如,当调用 getTheta() 时,具有 x:10 和 y:10 的向量将 return 的值为 1/4 PI。

  4. bearingTo(target) 应该 return 一个大于或等于零且小于 2 PI 的值。该值表示与另一个向量的方位角。

测试代码表明,当您尝试将 (0,0) 处的一个点的方位角设置为 (10,0) 处的另一点时,它不会产生预期的结果,应该是 90 度或 1/2 PI 弧度。

同样,尝试从极坐标初始化向量会将 x 和 y 坐标设置为意外值。我尽量避免说 "incorrect values" 因为它不是不正确的,它只是不符合要求。

我把代码弄乱了很多,在这里添加 PI 的分数或在那里去掉,切换正弦和余弦,但所有这些事情只能解决部分问题,而不能解决整个问题。

最后我做了一个可以在线执行的代码版本http://tpcg.io/OYVB5Q

典型的极坐标 0 指向东方,它们指向 counter-clockwise。您的坐标从北方开始,可能顺时针方向。修复代码的最简单方法是首先使用以下公式在角度之间进行转换:

flippedAngle = π/2 - originalAngle

这个公式是对称的,因为它在 "your" 和 "standard" 坐标之间进行双向转换。因此,如果您将代码更改为:

    public double bearingTo(Vector target) {
        return Math.PI/2 - (Math.atan2(target.getY() - y, target.getX() - x));
    }

    public static Vector fromPolar(double magnitude, double angle) {
        double flippedAngle = Math.PI/2 - angle;
        return new Vector(magnitude * Math.cos(flippedAngle),
                magnitude * Math.sin(flippedAngle));
    }

它开始作为您的 tests suggest 工作。您也可以应用一些三角学知识来不进行此 Math.PI/2 - angle 计算,但我不确定这是否真的使代码更清晰。

如果你想让你的 "bearing" 在 [0, 2*π] 范围内(即总是 non-negative),你可以使用这个版本的 bearingTo(也修复了 theta):

public class Vector {
    private final double x;
    private final double y;

    public Vector(double xIn, double yIn) {
        x = xIn;
        y = yIn;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public double getR() {
        return Math.sqrt((x * x) + (y * y));
    }

    public double getTheta() {
        return flippedAtan2(y, x);
    }

    public double bearingTo(Vector target) {
        return flippedAtan2(target.getY() - y, target.getX() - x);
    }

    public static Vector fromPolar(double magnitude, double angle) {
        double flippedAngle = flipAngle(angle);
        return new Vector(magnitude * Math.cos(flippedAngle),
                magnitude * Math.sin(flippedAngle));
    }

    // flip the angle between 0 is the East + counter-clockwise and 0 is the North + clockwise
    // and vice versa
    private static double flipAngle(double angle) {
        return Math.PI / 2 - angle;
    }

    private static double flippedAtan2(double y, double x) {
        double angle = Math.atan2(y, x);
        double flippedAngle = flipAngle(angle);
        //  additionally put the angle into [0; 2*Pi) range from its [-pi; +pi] range
        return (flippedAngle >= 0) ? flippedAngle : flippedAngle + 2 * Math.PI;
    }
}