mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-05-23 03:19:55 +00:00
feat(layout_optimizer): add segment_obb_intersection_length (Cyrus-Beck clipping)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -183,6 +183,56 @@ def _point_in_convex(
|
||||
return True
|
||||
|
||||
|
||||
def segment_obb_intersection_length(
|
||||
p1: tuple[float, float],
|
||||
p2: tuple[float, float],
|
||||
corners: list[tuple[float, float]],
|
||||
) -> float:
|
||||
"""线段 p1-p2 与 OBB(凸多边形)的交集长度。
|
||||
|
||||
Cyrus-Beck 线段裁剪算法。corners 假定为 CCW 顺序(obb_corners 生成)。
|
||||
无交集返回 0.0。
|
||||
"""
|
||||
dx = p2[0] - p1[0]
|
||||
dy = p2[1] - p1[1]
|
||||
seg_len_sq = dx * dx + dy * dy
|
||||
if seg_len_sq < 1e-24:
|
||||
return 0.0
|
||||
|
||||
t_enter = 0.0
|
||||
t_exit = 1.0
|
||||
n = len(corners)
|
||||
|
||||
for i in range(n):
|
||||
ax, ay = corners[i]
|
||||
bx, by = corners[(i + 1) % n]
|
||||
# CCW 多边形边的外法线: (ey, -ex), e = b - a
|
||||
ex, ey = bx - ax, by - ay
|
||||
nx, ny = ey, -ex
|
||||
|
||||
denom = nx * dx + ny * dy
|
||||
numer = nx * (p1[0] - ax) + ny * (p1[1] - ay)
|
||||
|
||||
if abs(denom) < 1e-12:
|
||||
if numer > 0:
|
||||
return 0.0 # 在此边外侧且平行
|
||||
continue
|
||||
|
||||
t = -numer / denom
|
||||
if denom < 0:
|
||||
t_enter = max(t_enter, t) # 进入
|
||||
else:
|
||||
t_exit = min(t_exit, t) # 退出
|
||||
|
||||
if t_enter > t_exit:
|
||||
return 0.0
|
||||
|
||||
if t_enter >= t_exit:
|
||||
return 0.0
|
||||
|
||||
return (t_exit - t_enter) * math.sqrt(seg_len_sq)
|
||||
|
||||
|
||||
def obb_min_distance(
|
||||
corners_a: list[tuple[float, float]],
|
||||
corners_b: list[tuple[float, float]],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Tests for OBB (Oriented Bounding Box) geometry utilities."""
|
||||
import math
|
||||
import pytest
|
||||
from ..obb import obb_corners, obb_overlap, obb_min_distance
|
||||
from ..obb import obb_corners, obb_overlap, obb_min_distance, segment_obb_intersection_length
|
||||
|
||||
|
||||
class TestObbCorners:
|
||||
@@ -115,3 +115,42 @@ class TestObbMinDistance:
|
||||
a = obb_corners(0, 0, 2.0, 2.0, 0.0)
|
||||
b = obb_corners(2.0, 0, 2.0, 2.0, 0.0)
|
||||
assert obb_min_distance(a, b) == pytest.approx(0.0)
|
||||
|
||||
|
||||
class TestSegmentOBBIntersectionLength:
|
||||
"""segment_obb_intersection_length: Cyrus-Beck clipping."""
|
||||
|
||||
def test_segment_fully_outside(self):
|
||||
corners = obb_corners(0, 0, 2, 2, 0)
|
||||
length = segment_obb_intersection_length((-5, 3), (5, 3), corners)
|
||||
assert length == 0.0
|
||||
|
||||
def test_segment_fully_inside(self):
|
||||
corners = obb_corners(0, 0, 4, 4, 0)
|
||||
length = segment_obb_intersection_length((-0.5, 0), (0.5, 0), corners)
|
||||
assert abs(length - 1.0) < 1e-6
|
||||
|
||||
def test_segment_crosses_through(self):
|
||||
corners = obb_corners(0, 0, 2, 2, 0)
|
||||
length = segment_obb_intersection_length((-5, 0), (5, 0), corners)
|
||||
assert abs(length - 2.0) < 1e-6
|
||||
|
||||
def test_segment_partial_overlap(self):
|
||||
corners = obb_corners(0, 0, 2, 2, 0)
|
||||
length = segment_obb_intersection_length((0, 0), (5, 0), corners)
|
||||
assert abs(length - 1.0) < 1e-6
|
||||
|
||||
def test_rotated_obb(self):
|
||||
corners = obb_corners(0, 0, 2, 2, math.pi / 4)
|
||||
length = segment_obb_intersection_length((-3, 0), (3, 0), corners)
|
||||
expected = 2 * math.sqrt(2)
|
||||
assert abs(length - expected) < 1e-4
|
||||
|
||||
def test_zero_length_segment(self):
|
||||
corners = obb_corners(0, 0, 2, 2, 0)
|
||||
assert segment_obb_intersection_length((0, 0), (0, 0), corners) == 0.0
|
||||
|
||||
def test_parallel_outside(self):
|
||||
corners = obb_corners(0, 0, 2, 2, 0)
|
||||
length = segment_obb_intersection_length((-5, 2), (5, 2), corners)
|
||||
assert length == 0.0
|
||||
|
||||
Reference in New Issue
Block a user