""" Vectorized line-segment and line-polygon intersection checks. All operations use NumPy for batch processing. """ import numpy as np from typing import Tuple def line_segments_intersect_batch( p1: np.ndarray, p2: np.ndarray, segments_start: np.ndarray, segments_end: np.ndarray, ) -> Tuple[np.ndarray, np.ndarray]: """Check if line p1->p2 intersects with N segments. Args: p1, p2: shape (2,) segments_start, segments_end: shape (N, 2) Returns: intersects: bool array (N,) t_values: parameter along p1->p2 (N,) """ d = p2 - p1 seg_d = segments_end - segments_start cross = d[0] * seg_d[:, 1] - d[1] * seg_d[:, 0] parallel_mask = np.abs(cross) < 1e-10 cross_safe = np.where(parallel_mask, 1.0, cross) dp = p1 - segments_start t = (dp[:, 0] * seg_d[:, 1] - dp[:, 1] * seg_d[:, 0]) / cross_safe u = (dp[:, 0] * d[1] - dp[:, 1] * d[0]) / cross_safe intersects = ~parallel_mask & (t >= 0) & (t <= 1) & (u >= 0) & (u <= 1) return intersects, t def line_intersects_polygons_batch( p1: np.ndarray, p2: np.ndarray, polygons_x: np.ndarray, polygons_y: np.ndarray, polygon_lengths: np.ndarray, max_polygons: int = 30, ) -> Tuple[np.ndarray, np.ndarray]: """Check if line p1->p2 intersects multiple polygons. Uses bounding-box pre-filter to limit work when polygon count is large. Args: p1, p2: shape (2,) polygons_x, polygons_y: flattened vertex arrays polygon_lengths: vertices per polygon (num_polygons,) max_polygons: only check nearest N polygons Returns: intersects: bool (num_polygons,) min_distances: distance to first hit (num_polygons,) """ num_polygons = len(polygon_lengths) if num_polygons == 0: return np.array([], dtype=bool), np.array([]) intersects = np.zeros(num_polygons, dtype=bool) min_t = np.full(num_polygons, np.inf) # Pre-filter: bounding box check if num_polygons > max_polygons: buf = 50.0 line_min_x = min(p1[0], p2[0]) - buf line_max_x = max(p1[0], p2[0]) + buf line_min_y = min(p1[1], p2[1]) - buf line_max_y = max(p1[1], p2[1]) + buf nearby_mask = np.zeros(num_polygons, dtype=bool) vi = 0 for i, length in enumerate(polygon_lengths): if length >= 3: cx = polygons_x[vi] cy = polygons_y[vi] if line_min_x <= cx <= line_max_x and line_min_y <= cy <= line_max_y: nearby_mask[i] = True vi += length nearby_indices = np.where(nearby_mask)[0] if len(nearby_indices) > max_polygons: nearby_mask = np.zeros(num_polygons, dtype=bool) nearby_mask[nearby_indices[:max_polygons]] = True else: nearby_mask = np.ones(num_polygons, dtype=bool) idx = 0 for i, length in enumerate(polygon_lengths): if length < 3 or not nearby_mask[i]: idx += length continue px = polygons_x[idx:idx + length] py = polygons_y[idx:idx + length] starts = np.stack([px, py], axis=1) ends = np.stack([np.roll(px, -1), np.roll(py, -1)], axis=1) edge_intersects, t_vals = line_segments_intersect_batch(p1, p2, starts, ends) if np.any(edge_intersects): intersects[i] = True min_t[i] = np.min(t_vals[edge_intersects]) idx += length line_length = np.linalg.norm(p2 - p1) min_distances = min_t * line_length return intersects, min_distances