You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python中射线与线段交点检测失效,无法过滤穿过图形的无效射线

Python中射线与线段交点检测失效,无法过滤穿过图形的无效射线

我仔细看了你的代码和问题描述,发现几个核心问题导致射线无法被正确标记为无效,下面逐一说明并给出修复方案:

问题分析

  1. 射线生成逻辑严重冗余且错误
    generate_rays方法中,你遍历三角形的每条线段,每遍历一次就创建一个相同的射线并添加到列表里,这会导致同一个射线被重复添加3次(三角形3条边),而且每个射线绑定的line_segment是当前循环的线段,而不是采样点实际所在的线段。这直接导致lies_on方法无法正确判断射线是否属于当前检测的线段。

  2. 交点检测未处理边界情况
    你的segment_intersection函数实现的是标准的线段相交判断,但没有处理交点恰好在线段端点的情况,而且射线本质是从采样点到x轴的线段,但你需要确保检测的是射线(或者说这条线段)是否和三角形的其他边相交(排除自身所在的边)。

  3. 无效射线过滤的遍历与删除逻辑有问题
    你在倒序遍历射线列表时直接删除元素,但因为前面的重复创建,即使删除了一个,还有重复的射线残留,而且颜色标记的逻辑也因为重复添加而无法正确显示。

修复后的代码

import numpy as np
import matplotlib.pyplot as plt
import random

PI = np.pi
POINT_SIZE = 20
NUM_POINTS = 1
NUM_RAYS = 2

all_segments = []

class Ray:
    def __init__(self, start, end, parent_segment):
        self.start = start
        self.end = end
        self.color = 'limegreen'
        self.parent_segment = parent_segment  # 射线起点所在的线段

    def lies_on(self, segment):
        # 用集合判断线段是否相同(忽略端点顺序)
        seg_set = set((tuple(p) for p in segment))
        parent_set = set((tuple(p) for p in self.parent_segment))
        return seg_set == parent_set

    def intersects_with(self, segment):
        return segment_intersection([self.start, self.end], segment)

    def plot(self):
        width = 1
        if self.color == 'red':
            width = 5
        plt.arrow(self.start[0], self.start[1], self.end[0] - self.start[0], self.end[1] - self.start[1],
                  head_width=0.2, head_length=0.2, fc=self.color, ec=self.color, linewidth=width, zorder=4)

class Triangle:
    def __init__(self, vertices):
        self.vertices = vertices
        self.points = []
        self.rays = []
        self.line_segments = []
        self.get_line_segments()

    def get_line_segments(self):
        for i in range(len(self.vertices)):
            segment = [self.vertices[i], self.vertices[(i + 1) % len(self.vertices)]]
            self.line_segments.append(segment)
            all_segments.append(segment)

    def plot(self):
        colors = ['gainsboro', 'lightcoral', 'lightsalmon', 'sandybrown', 'gold',
                  'darkseagreen', 'turquoise', 'skyblue', 'mediumpurple', 'thistle']
        color = random.choice(colors)
        a, b, c = np.array(self.vertices[0]), np.array(self.vertices[1]), np.array(self.vertices[2])
        plt.fill([a[0], b[0], c[0]], [a[1], b[1], c[1]], color=color, alpha=0.5)
        plt.plot([a[0], b[0]], [a[1], b[1]], color=color, linewidth=2)
        plt.plot([b[0], c[0]], [b[1], c[1]], color=color, linewidth=2)
        plt.plot([c[0], a[0]], [c[1], a[1]], color=color, linewidth=2)

    def sample_points(self):
        for i in range(len(self.vertices)):
            start = np.array(self.vertices[i])
            end = np.array(self.vertices[(i + 1) % len(self.vertices)])
            t_values = np.random.uniform(0, 1, NUM_POINTS)
            for t in t_values:
                point = (1 - t) * start + t * end
                # 同时保存采样点和它所在的线段
                self.points.append( (point, self.line_segments[i]) )

    def plot_points(self):
        for point, _ in self.points:
            plt.scatter(point[0], point[1], color='black', s=POINT_SIZE, zorder=5)

    def generate_rays(self):
        for center, parent_segment in self.points:
            angle_spacing = np.pi / NUM_RAYS
            # 只生成向下指向x轴的射线(角度范围-π到0)
            angles = np.linspace(-np.pi, 0, NUM_RAYS, endpoint=False)
            # 调整角度偏移,让射线均匀分布
            angles -= angle_spacing / 2
            for angle in angles:
                direction = np.array([np.cos(angle), np.sin(angle)])
                if direction[1] != 0:
                    t = -center[1] / direction[1]
                    x_intersect = center[0] + t * direction[0]
                    end_point = [x_intersect, 0]
                    # 只创建一次射线,绑定正确的父线段
                    ray = Ray(center.tolist(), end_point, parent_segment)
                    self.rays.append(ray)

    def plot_rays(self):
        for ray in self.rays:
            ray.plot()

def graph(*commands):
    plt.figure(figsize=(8, 6))
    plt.axhline(0, color='black', linewidth=3)
    plt.axvline(0, color='black', linewidth=3)
    plt.grid(color='gray', linestyle='--', linewidth=1)
    plt.axis((-10, 20, -1, 12))
    for func in commands:
        func()
    plt.show()

def ccw(A, B, C):
    # 处理浮点数精度问题,避免因为微小误差导致判断错误
    cross = (C[1] - A[1]) * (B[0] - A[0]) - (B[1] - A[1]) * (C[0] - A[0])
    return np.isclose(cross, 0) or cross > 0

def segment_intersection(segment1, segment2):
    A, B = segment1
    C, D = segment2
    # 跳过端点重合的情况(根据需求可调整)
    if tuple(A) == tuple(C) or tuple(A) == tuple(D) or tuple(B) == tuple(C) or tuple(B) == tuple(D):
        return False
    # 标准线段相交判断
    return ccw(A, C, D) != ccw(B, C, D) and ccw(A, B, C) != ccw(A, B, D)

def check_rays(triangle):
    valid_rays = []
    for ray in triangle.rays:
        is_valid = True
        for segment in all_segments:
            if ray.lies_on(segment):
                continue  # 跳过射线所在的线段
            if ray.intersects_with(segment):
                is_valid = False
                ray.color = 'red'
                break
        if is_valid:
            valid_rays.append(ray)
    # 替换为有效射线列表
    triangle.rays = valid_rays
    return True

triangles = [
             Triangle([[8, 10], [10, 4], [6, 2]]),
            #  Triangle([[4, 6], [4, 2], [0, 1]]),
            #  Triangle([[13, 2], [14, 1], [11, 2]])
            ]

for triangle in triangles:
    triangle.sample_points()
    triangle.generate_rays()
    check_rays(triangle)

graph(
  lambda: [triangle.plot() for triangle in triangles],
  lambda: [triangle.plot_points() for triangle in triangles],
  lambda: [triangle.plot_rays() for triangle in triangles]
)

关键修改说明

  1. 修复射线生成逻辑:在sample_points中同时保存采样点和它所在的线段,生成射线时只创建一次,绑定正确的父线段,避免重复创建。
  2. 处理浮点数精度问题:使用np.isclose处理ccw计算的精度误差,避免因为浮点精度导致交点判断错误。
  3. 优化无效射线过滤:改用新列表保存有效射线,避免遍历原列表时删除元素导致的索引混乱。
  4. 简化角度计算:调整射线角度范围为-π到0(确保向下指向x轴),让射线分布更合理。

效果展示

射线检测修复效果示例

现在穿过三角形的射线会被标记为红色并从有效列表中排除,完全符合你的预期,而且代码可以无缝扩展到多个三角形的场景。

备注:内容来源于stack exchange,提问作者s123

火山引擎 最新活动