mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-05-24 18:30:00 +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
|
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(
|
def obb_min_distance(
|
||||||
corners_a: list[tuple[float, float]],
|
corners_a: list[tuple[float, float]],
|
||||||
corners_b: list[tuple[float, float]],
|
corners_b: list[tuple[float, float]],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Tests for OBB (Oriented Bounding Box) geometry utilities."""
|
"""Tests for OBB (Oriented Bounding Box) geometry utilities."""
|
||||||
import math
|
import math
|
||||||
import pytest
|
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:
|
class TestObbCorners:
|
||||||
@@ -115,3 +115,42 @@ class TestObbMinDistance:
|
|||||||
a = obb_corners(0, 0, 2.0, 2.0, 0.0)
|
a = obb_corners(0, 0, 2.0, 2.0, 0.0)
|
||||||
b = obb_corners(2.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)
|
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