首页 > 解决方案 > 从特定角度获取像素线

问题描述

我有一些问题。我正在使用pygame。我想获取以特定角度远离一个点的所有像素的线。

例如,我想获得以 18% 的角度远离点 (15,20) 的所有像素的线。

我知道有一些数学要做,但我不知道如何在 python 中做到这一点。你可以帮帮我吗?

标签: pythonpygame

解决方案


假设您希望从⟪some point⟫开始的直线中的像素以⟪some angle⟫。

首先让我们定义一些东西。

 origin_point  = ( 15, 20 )            # Co-ordinates of starting point
 project_angle = math.radians( 18.0 )  # Angle of projection in degrees

我们需要将 Python 数学三角函数的角度从度数更改为弧度数。不仅 Python 使用弧度,大多数编程语言都使用弧度。

我们要定义从origin_point方向 的线project_angle。通过计算线的端点,它允许我们计算像素。那么这条线在哪里结束呢?无穷?可能屏幕的边缘会做。

我们需要计算出与屏幕边缘形成的线的交点,因此我们可以使用任何长度的线,只要理论上大于屏幕的最大可能线长(即对角线:diagonal_length = √(window_width² + window_length²))。所以任何“足够长”的长度都可以,所以我们会作弊并使用常数 10000。

MAX_LENGTH = 10000

现在对于那个坐标......使用角度的正弦和余弦,乘以长度来计算该点:

final_x = start_x + ( length * math.cos( angle_of_projection ) )
final_y = start_y + ( length * math.sin( angle_of_projection ) )

把它变成一个函数:

def getEndOfLine( start_point, angle_of_projection, length ):
    """ Given a co-ordinate, projection angle, and line length, return
        the co-ordinate of the end of line """
    start_x, start_y = start_point
    final_x = start_x + length * math.cos( angle_of_projection )
    final_y = start_y + length * math.sin( angle_of_projection )
    return ( final_x, final_y )

使用此函数,我们现在可以执行以下操作:

end_point = getEndOfLine( origin_point, project_angle, MAX_LENGTH )

给我们两点来定义这条线。所以,现在我们有了一条线,我们可以将它与屏幕边缘相交。因为我们的队伍很长,所以保证会在某个地方退出屏幕。

屏幕的边缘在哪里 - 这些很容易知道:

top_side    = [ ( 0, 0 ), ( SCREEN_WIDTH-1, 0 ) ]
bottom_side = [ ( 0, SCREEN_HEIGHT-1 ), ( SCREEN_WIDTH-1, SCREEN_HEIGHT-1 ) ]
left_side   = [ ( 0, 0 ), ( SCREEN_WIDTH-1, SCREEN_HEIGHT-1 ) ]
right_side  = [ ( SCREEN_WIDTH-1, 0 ), ( SCREEN_WIDTH-1, SCREEN_HEIGHT-1 ) ]
all_sides = [ top_side, right_side, bottom_side, left_side ]

现在我们需要将斜线与屏幕边缘线相交。我不想在这里过多地讨论数学,但如果两条直线、无限长的线不平行,它们必须相交。所以首先确定你的线不平行,然后使用行列式方法计算交点。这些线可能在屏幕外相交,我们不关心这些点。

def intersectionPoint( x1,y1, x2,y2, x3,y3, x4,y4 ):
    #Use determinant method, as per
    #Ref: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
    Px = ((((x1*y2)-(y1*x2))*(x3 - x4)) - ((x1-x2)*((x3*y4)-(y3*x4)))) / (((x1-x2)*(y3-y4)) - ((y1-y2)*(x3-x4)))
    Py = ((((x1*y2)-(y1*x2))*(y3 - y4)) - ((y1-y2)*((x3*y4)-(y3*x4)))) / (((x1-x2)*(y3-y4)) - ((y1-y2)*(x3-x4)))
    return Px,Py

def getIntersectionPoint( line_start, line_end, screen_edges ):
    x1, y1 = line_start
    x2, y2 = line_end

    for edge in screen_edges:
        x3, y3 = edge[0]  # line forming the screen edge
        x4, y4 = edge[1]
        is_parallel = (((x1-x2)*(y3-y4)) - ((y1-y2)*(x3-x4)) == 0)

        if ( not is_parallel ):
           int_x, int_y = intersectionPoint( x1,y1, x2,y2, x3,y3, x4,y4 )
           # but is the intersection point on-screen (otherwise we don't care)
           if ( int_x >= 0 and int_y >= 0 and int_x < SCREEN_WIDTH and int_y < SCREEN_HEIGHT ):
               return ( int_x, int_y )

    return None  # should never happen

所以现在使用这个函数,我们可以确定屏幕边缘的点:

# Work out the long long line:
peliminary_point = getEndOfLine( origin_point, project_angle, MAX_LENGTH )
print( "Peliminary End Point from %3.1f° is (%3.1f,%3.1f)" % ( project_angle, peliminary_point[0], peliminary_point[1] ) )

# Work out where this line exits the screen/window:
end_point = getIntersectionPoint( origin_point, peliminary_point, all_sides )
print( "End Point is (%3.1f,%3.1f)" % ( end_point[0], end_point[1] ) )

对于 200x200 的窗口,MAX_LENGTH 为 10000,我得到:

从 0.31° 开始的初步终点是 (38052.3,12370.7)
终点是 (199.0,71.4)

现在我们有了线末端的坐标,很容易使用Bresenhnam 的中点线算法来计算像素。最好去阅读一下,我不打算在这里解释(甚至不确定我能不能)。

def _plotLineLow( x0,y0, x1,y1 ):
    points = []
    dx = x1 - x0
    dy = y1 - y0
    yi = 1
    if dy < 0:
        yi = -1
        dy = -dy
    D = 2*dy - dx
    y = y0

    for x in range( x0, x1 ):
        points.append( (x,y) )
        if D > 0:
           y = y + yi
           D = D - 2*dx
        D = D + 2*dy
    return points

def _plotLineHigh( x0,y0, x1,y1 ):
    points = []
    dx = x1 - x0
    dy = y1 - y0
    xi = 1
    if dx < 0:
        xi = -1
        dx = -dx
    D = 2*dx - dy
    x = x0

    for y in range( y0, y1 ):
        points.append( (x,y) )
        if D > 0:
            x = x + xi
            D = D - 2*dy
        D = D + 2*dx
    return points

def getMidLinePoints( pointA, pointB ):
    x0, y0 = int( pointA[0] ), int( pointA[1] )
    x1, y1 = int( pointB[0] ), int( pointB[1] )
    points = []
    if ( abs(y1 - y0) < abs(x1 - x0) ):
        if ( x0 > x1 ):
            points += _plotLineLow( x1, y1, x0, y0 )
        else:
            points += _plotLineLow( x0, y0, x1, y1 )
    else:
        if ( y0 > y1 ):
            points += _plotLineHigh( x1, y1, x0, y0 )
        else:
            points += _plotLineHigh( x0, y0, x1, y1 )
    return points

添加此代码,给出沿线从origin_pointatproject_angle到屏幕边缘的像素坐标:

# Work out the long long line:
peliminary_point = getEndOfLine( origin_point, project_angle, MAX_LENGTH )
print( "Peliminary End Point from %3.1f° is (%3.1f,%3.1f)" % ( project_angle, peliminary_point[0], peliminary_point[1] ) )

# Work out where this line exits the screen/window:
end_point = getIntersectionPoint( origin_point, peliminary_point, all_sides )
print( "End Point is (%3.1f,%3.1f)" % ( end_point[0], end_point[1] ) )

line_points = getMidLinePoints( origin_point, end_point )
print( "Line Points are: " + str( line_points ) )

这给了我(这次是在 100x100 窗口上):

从 0.31° 开始的初步终点是 (9520.6,3100.2)
终点是 (99.0,38.9)
线点为:[(10, 10), (11, 10), (12, 11), (13, 11), (14, 11), (15, 12), (16, 12), (17, 12), (18, 13), (19, 13), (20, 13), (21, 13), (22, 14), (23, 14), (24, 14), (25, 15) , (26, 15), (27, 15), (28, 16), (29, 16), (30, 16), (31, 17), (32, 17), (33, 17), ( 34, 18), (35, 18), (36, 18), (37, 18), (38, 19), (39, 19), (40, 19), (41, 20), (42, 20), (43, 20), (44, 21), (45, 21), (46, 21), (47, 22), (48, 22), (49, 22), (50, 23) , (51, 23), (52, 23), (53, 24), (54, 24), (55, 24), (56, 24), (57, 25), (58, 25), ( 59, 25), (60, 26), (61, 26), (62, 26), (63, 27), (64, 27), (65, 27), (66, 28), (67, 28), (68, 28), (69, 29), (70, 29), (71, 29), (72, 30), (73, 30), (74, 30), (75, 30) , (76, 31), (77, 31), (78, 31), (79, 32), (80, 32), (81, 32), (82, 33), (83, 33), ( 84, 33), (85, 34), (86, 34), (87, 34), (88, 35), (89, 35), (90, 35), (91, 35), (92, 36), (93, 36),(94, 36), (95, 37), (96, 37), (97, 37), (98, 38)]

很简单嘿嘿! ;)


推荐阅读