计算一条直线与x轴之间的夹角

64

我目前正在开发一个简单的Android 2D游戏。屏幕中央有一个静止的物体,我想让这个物体旋转并指向用户触摸的区域。我有代表屏幕中心的常量坐标,可以获取用户点击的点的坐标。我正在使用这个论坛中概述的公式:如何获取两点之间的角度?

  • 它说:“如果您想要定义由这两个点确定的线和水平轴之间的角度:

double angle = atan2(y2 - y1, x2 - x1) * 180 / PI;".
  • 我已经实现了这个功能,但是我认为我在屏幕坐标系下工作会导致计算错误,因为Y坐标被反转了。我不确定这是否是正确的做法,如果有其他想法或建议,请告诉我。


  • 6
    从技术上讲,您无法得到两个“点”之间的角度。不过,您可以得到两个“向量”之间的角度。 - ChrisF
    22
    他的意思很明确,是指“连接两个点之间的直线与水平轴之间的夹角”。 - Jason Hall
    2
    抱歉,让我重新表达我的标题,如何获得由这两个点定义的直线与通过屏幕中心的物体的水平轴之间的角度? - kingrichard2005
    8个回答

    101

    假设:x轴是水平轴,向右移动时增加。 y轴是垂直轴,从底部向上增加。 (touch_x,touch_y)是用户选择的点。 (center_x,center_y)是屏幕中心点。 theta是逆时针从+x轴测量的角度。则:

    delta_x = touch_x - center_x
    delta_y = touch_y - center_y
    theta_radians = atan2(delta_y, delta_x)
    

    编辑:您在评论中提到y从上到下增加。在这种情况下,

    delta_y = center_y - touch_y
    

    但更准确的描述是将(touch_x, touch_y)相对于(center_x, center_y)表达为极坐标。正如ChrisF所提到的,"两点之间的角度"的概念并不明确定义。


    谢谢Jim,无论我使用笛卡尔坐标系还是极坐标系,我怎么知道计算出的角度是基于穿过我的对象的水平线,而不是从左上角的原点出发的屏幕顶部的水平线? - kingrichard2005
    1
    @kingrichard2005:由于delta_x和delta_y是相对于中心点(即您的对象位置)计算的,因此theta也将相对于同一点计算。 - Jim Lewis
    如果y从上往下增加,这会如何改变? - Scorb
    @Scorb 切换从上到下或从下到上增加Y的方式,只需要在delta_y计算中翻转符号即可。对于从上到下,您可以使用我在编辑中添加的公式。 - Jim Lewis

    33

    因为自己需要类似的功能,所以经过长时间的苦思冥想,我编写了下面的函数

    /**
     * Fetches angle relative to screen centre point
     * where 3 O'Clock is 0 and 12 O'Clock is 270 degrees
     * 
     * @param screenPoint
     * @return angle in degress from 0-360.
     */
    public double getAngle(Point screenPoint) {
        double dx = screenPoint.getX() - mCentreX;
        // Minus to correct for coord re-mapping
        double dy = -(screenPoint.getY() - mCentreY);
    
        double inRads = Math.atan2(dy, dx);
    
        // We need to map to coord system when 0 degree is at 3 O'clock, 270 at 12 O'clock
        if (inRads < 0)
            inRads = Math.abs(inRads);
        else
            inRads = 2 * Math.PI - inRads;
    
        return Math.toDegrees(inRads);
    }
    

    27

    这里有几个答案试图解释“屏幕”问题,即左上角0, 0,而右下角为(正的)屏幕宽度,屏幕高度。大多数网格将Y轴视为在X之上而不是之下。

    以下方法将与屏幕值一起使用而不是“网格”值。与期望的答案唯一的区别是Y值被反转。

    /**
     * Work out the angle from the x horizontal winding anti-clockwise 
     * in screen space. 
     * 
     * The value returned from the following should be 315. 
     * <pre>
     * x,y -------------
     *     |  1,1
     *     |    \
     *     |     \
     *     |     2,2
     * </pre>
     * @param p1
     * @param p2
     * @return - a double from 0 to 360
     */
    public static double angleOf(PointF p1, PointF p2) {
        // NOTE: Remember that most math has the Y axis as positive above the X.
        // However, for screens we have Y as positive below. For this reason, 
        // the Y values are inverted to get the expected results.
        final double deltaY = (p1.y - p2.y);
        final double deltaX = (p2.x - p1.x);
        final double result = Math.toDegrees(Math.atan2(deltaY, deltaX)); 
        return (result < 0) ? (360d + result) : result;
    }
    

    1
    刚刚用不同的x和y值进行了测试;这个答案可以产生准确的屏幕坐标结果。 - Velusamy Velu
    如果结果小于0,为什么要将360d添加到结果中?结果怎么可能会小于0呢? - user7697718
    1
    @Mike:在样例代码中,如果没有对负数进行修正,结果将是“-45”而不是“315”。两个值都是正确的,但根据我的记忆,我只想要正数和“0”。 - Tigger
    @Tigger 好的,我现在明白了 :) 谢谢 - user7697718

    2
    fun calculateAngle(
        touchX: Float,
        touchY: Float,
        centerX: Float,
        centerY: Float
    ): Float {
        val deltaX = centerX - touchX
        val deltaY = centerY - touchY
        return Math.toDegrees(atan2(deltaY.toDouble(), deltaX.toDouble())).toFloat()
    }
    

    此函数将返回如下值: 如果我们将返回值加上+ 180,那么我们将得到从右到左的值,如下所示: 360(<=> 0) -> 45 -> 90 -> 135 -> 180 -> 225 -> 270 -> 315 这类似于我们drawArc时使用的角度。

    一些JavaScript实现:function calcAngle(ax, ay, bx, by){var dx = bx - ax; var dy = by - ay; return Math.atan2(dy, dx) * 180 / Math.PI;} - Rodrigo Polo

    2
    在安卓中,我使用 Kotlin 实现了以下操作:
    private fun angleBetweenPoints(a: PointF, b: PointF): Double {
            val deltaY = abs(b.y - a.y)
            val deltaX = abs(b.x - a.x)
            return Math.toDegrees(atan2(deltaY.toDouble(), deltaX.toDouble()))
        }
    

    1
    "原点位于屏幕左上角,Y坐标向下增加,而X坐标向右增加。我的问题是,我是否需要在应用上述公式之前将屏幕坐标转换为笛卡尔坐标?如果您正在使用笛卡尔坐标计算角度,并且两个点都在第一象限(其中x>0且y>0),情况与屏幕像素坐标相同(除了倒置的Y)。将屏幕像素坐标转换为笛卡尔坐标并不会真正改变角度。"

    1
    使用pygame:
    dy = p1.y - p2.y
    dX = p2.x - p1.x
    
    rads = atan2(dy,dx)
    degs = degrees(rads)
    if degs < 0 :
       degs +=90
    

    它对我起作用了


    0
    如果你想以弧度形式获取斜率 -> m = math.atan2((jy-iy),(jx-ix)) 并且以角度形式 ---> m = math.atan2((jy-iy),(jx-ix))*180/math.pi

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接