Расчет пересечений линий с Python дал неожиданный результат - PullRequest
1 голос
/ 28 мая 2020

У меня есть программа моделирования лучей, которая находит пересечения луча и краев многоугольника. В моем фрагменте кода ниже у меня есть луч и край в виде y = mx + b линии. Я определил квадрат по его вершинам ((50, 50), (50, 70), (70, 70), (70, 50)) и направил лучи к каждой вершине, и моя программа вычислила пересечение с каждой вершиной, кроме (70, 70) и (70, 50). В последнем случае луч, казалось, «проскользнул» через эту вершину и пересек линию, проходящую через точки (50, 50) и (50, 70) в неожиданной точке (49.99999999999999 16.666666666666643). Чтобы уточнить, вот все пересечения, обнаруженные моей программой:

(50.0, 50.00000000000001) # Ray was cast towards (50, 50)
(50.0, 70.0) # Ray was cast towards (50, 70)
(50.0, 50.00000000000001) # Ray was cast towards (70, 70). Also unexpected
(49.99999999999999, 16.666666666666643) # Ray was cast towards (70, 50) Unexpected intersection value

В моем файле objects.py:

from math import atan, pi

class Ray:
    def __init__(self, origin, direction):
        self.origin = origin
        self.direction = direction  # radians

        self.endpoint = None

        self.hit = False

    def set_endpoint(self, point):
        self.endpoint = point

    def set_hit(self):
        self.hit = True


class Line:
    def __init__(self, endpoint1, endpoint2):
        self.p1 = endpoint1
        self.p2 = endpoint2

    def direction(self):
        delta_x = self.p2[0] - self.p1[0]
        delta_y = self.p2[1] - self.p1[1]

        if delta_x == 0:  # Undefined slope

            if delta_y > 0:
                return pi / 2

            else:
                return 3 * pi / 2

        else:
            return atan(delta_y / delta_x)


class Polygon:
    def __init__(self, vertices):
        self.vertices = vertices


    def edges(self):
        edges = []

        for i in range(len(self.vertices)):
            # We mod the endpoint point of the line by the amount of vertices
            # since we want the endpoint of our last edge to be the first vertex
            edges.append(Line(self.vertices[i], self.vertices[(i + 1) % len(self.vertices)]))

        return edges

И в моем файле caster.py:

from ART import objects
from math import tan


class ShadowCaster:
    def __init__(self, source, polygons):
        self.source = source
        self.polygons = polygons
        self.rays = []

        print(self.polygons)

    def cast_rays(self):
        for polygon in self.polygons:
            for vertex in polygon.vertices:
                direction_to_vertex = objects.Line(self.source, vertex).direction()
                ray = objects.Ray(self.source, direction_to_vertex)

                self.rays.append(ray)

    def process_endpoints(self):
        for ray in self.rays:
            for polygon in self.polygons:
                for edge in polygon.edges():
                    # We are given the endpoints and direction of both the ray and the edge. Find intersection.
                    # We want to obtain the general form y = mx + b for the ray and edge.

                    # Given: y, m, x; solve for b
                    # b = y - mx

                    if not ray.hit:
                        ray_x = ray.origin[0]
                        ray_y = ray.origin[1]
                        ray_m = tan(ray.direction)

                        ray_b = ray_y - ray_m * ray_x

                        edge_x = edge.p1[0]  # Using either p1 or p2 is fine since the line passes through both.
                        edge_y = edge.p1[1]
                        edge_m = tan(edge.direction())

                        edge_b = edge_y - edge_m * edge_x

                        # General case
                        # {y = ax + b
                        # {y = cx + d
                        #
                        # => ax + b = cx + d
                        # => x(a - c) = d - b
                        # => x = (d - b) / (a - c) therefore y = a((d - b) / (a - c)) + b

                        intersect_x = (edge_b - ray_b) / (ray_m - edge_m)
                        intersect_y = ray_m * intersect_x + ray_b

                        print(intersect_x, intersect_y)

                        ray.set_endpoint((intersect_x, intersect_y))
                        ray.set_hit()

l oop, который я запускаю:

caster = engine.ShadowCaster(origin=(100, 100), polygons=[objects.Polygon(((50, 50), (50, 70), (70, 70), (70, 50)))])


while 1:
    caster.cast_rays()
    caster.process_endpoints()

Есть предложения о том, что я мог сделать неправильно?

1 Ответ

1 голос
/ 28 мая 2020

После неутешительного количества возни с целью запуска вашего «минимально воспроизводимого примера» и выполнения некоторой отладки, проблема отсутствует. c: вы фактически не проверяете, находится ли точка пересечения между линия луча и линия кромки на самом деле находятся внутри линейного сегмента кромки - вы просто предполагаете, что первое пересечение является попаданием, то есть безусловным:

ray.set_endpoint((intersect_x, intersect_y))
ray.set_hit()

и как только это первое пересечение Если пересечение "найдено", то дальнейшие тесты пересечения не выполняются, хотя ваш код продолжает повторять их, что кажется ненужным. В любом случае в результате вы всегда показываете «пересечения» только с первым краем многоугольника.

Чтобы исправить это, вам нужно добавить тест пересечения луча с краем. Вам необходимо разрешить точность с плавающей запятой (im) и округление, то есть пересечение вычисляется как небольшое небольшое расстояние за пределами границ края (но не в том случае, если это было бы лучшим пересечением с другим краем).

Кстати, одна проблема с использованием общей формы y = mx + b заключается в том, что она не является надежной, когда линия ~ вертикальная - учитывая, что у вас есть две точки на каждой строке, вы могли бы быть безопаснее, используя параметр c form y = p * ( y2-y1) + y1 и x = p * (x2-x1) + x1, где 0,0 <= p <= 1,0, что, к счастью, также может упростить обнаружение пересечения без использования функций тригонометрии c, см. под заголовком «Учитывая две точки на каждая строка "здесь <a href="https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection" rel="nofollow noreferrer">https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection

Также, если вам нужно использовать направления линий, то использование math.atan2 более надежно, чем math.atan для описания направления линий - вам не нужно защита кода для вертикальной линии, где dx равно ~ 0, и поскольку он знает знаки dy и dx, он знает квадрант направления линии и возвращает значение в диапазоне +/- pi

...