曲线曲面求交解析方案-平面+曲线

曲线曲面求交解析方案-平面+曲线

文章目录

  • 曲线曲面求交解析方案-平面+曲线
    • [一. 通用约定](#一. 通用约定)
      • [1. 输入对象](#1. 输入对象)
      • [2. 容差规则](#2. 容差规则)
      • [3. 结果规则](#3. 结果规则)
    • [二. 直线与平面求交](#二. 直线与平面求交)
      • [1. 有限线段的端点距离分类](#1. 有限线段的端点距离分类)
      • [2. 穿越时的精确交点](#2. 穿越时的精确交点)
      • [3. 无限直线情况](#3. 无限直线情况)
    • [三. 圆/椭圆与平面求交](#三. 圆/椭圆与平面求交)
      • [1. 为什么不直接用两平面交线与圆/椭圆求交](#1. 为什么不直接用两平面交线与圆/椭圆求交)
      • [2. 统一参数表达](#2. 统一参数表达)
      • [3. 先处理平面平行/共面](#3. 先处理平面平行/共面)
      • [4. 非平行时用最大/最小有向距离分类](#4. 非平行时用最大/最小有向距离分类)
      • [5. 交点数量分类](#5. 交点数量分类)
        • [5.1 无交](#5.1 无交)
        • [5.2 一个切触/容差接触点](#5.2 一个切触/容差接触点)
        • [5.3 两个交点](#5.3 两个交点)
      • [6. 容差模糊区处理](#6. 容差模糊区处理)
      • [7. 伪代码](#7. 伪代码)
    • [附录 A:椭圆到平面的最大/最小有向距离点推导](#附录 A:椭圆到平面的最大/最小有向距离点推导)
    • [附录 B:两个交点参数 `t = phi ± acos(-D / S)` 的推导](#附录 B:两个交点参数 t = phi ± acos(-D / S) 的推导)

本文档单独记录直线、圆、椭圆与平面的解析求交方案。核心目标是用原始曲线到目标平面的有向距离做稳定分类,避免浅夹角情况下直接构造两平面交线导致的容差漏交。

一. 通用约定

1. 输入对象

曲线对象:

  • LGKLine3d
  • LGKCircle3d
  • LGKEllipse3d

曲面对象:

  • LGKPlane

2. 容差规则

  • 平行、垂直等方向关系使用系统角度容差 LGKConstants::AngleTolerance
  • 点到平面、点到曲线相交判断使用调用方传入的长度容差,例如曲线曲面求交 option 中的 m_tol
  • 参数范围过滤不能只用参数差扩张,应最终回到空间距离校验。
  • 所有候选点加入结果前必须同时满足:
    • 在曲线参数范围内;
    • 在平面参数范围内,如果平面被上层裁剪为有界参数域;
    • 点在原始曲线和原始平面上。

3. 结果规则

离散交点至少记录:

  • 3D 交点坐标;
  • 曲线参数 curveParam
  • 平面参数 uv
  • 求交类型:普通交点 Simple 或切触/容差接触 Tangent

如果曲线整体位于平面内,数学结果是重合区间,不是离散交点。当前点求交接口若不能直接返回 overlap,可暂时返回空点结果;后续应通过 overlap 结果扩展表达。

二. 直线与平面求交

适用对象:LGKLine3d 或有限线段与平面。

直线/线段与平面求交应优先使用点到平面的有向距离做分类,而不是先直接套代数公式。原因是有向距离分类可以自然处理无交、穿越、端点接触和重合,并且和圆/椭圆-平面求交的"先看距离范围,再生成交点"的思路一致。

1. 有限线段的端点距离分类

设线段为:

text 复制代码
L(t) = P0 + t (P1 - P0),    t ∈ [0, 1]

平面为:

text 复制代码
n · (X - Q) = 0

其中 n 是单位法向。计算两个端点到平面的有向距离:

text 复制代码
d0 = n · (P0 - Q)
d1 = n · (P1 - Q)

d0d1 和长度容差 tol 分类:

  • d0 > tol && d1 > tol:两个端点都在平面正侧,无交;
  • d0 < -tol && d1 < -tol:两个端点都在平面负侧,无交;
  • |d0| <= tol && |d1| <= tol:整条线段在平面容差内,返回重合区间;
  • 只有一个端点满足 |di| <= tol:返回该端点作为端点接触;
  • d0d1 一正一负:线段穿过平面,计算精确交点。

2. 穿越时的精确交点

当端点有向距离异号时,交点参数由线性插值得到:

text 复制代码
t = d0 / (d0 - d1)

交点为:

text 复制代码
P = P0 + t (P1 - P0)

这个公式来自:

text 复制代码
d(t) = (1 - t) d0 + t d1

d(t) = 0

text 复制代码
(1 - t) d0 + t d1 = 0

得到:

text 复制代码
t = d0 / (d0 - d1)

候选点加入结果前仍应检查:

  1. t 是否位于 [0, 1]
  2. |n · (P - Q)| <= tol
  3. 如果上层使用无限直线或射线,按对应参数域规则过滤。

3. 无限直线情况

如果输入是无限直线:

text 复制代码
L(t) = O + t D

计算:

text 复制代码
den = n · D
num = n · (O - Q)
  • |den| <= angleTol|num| <= tol:直线位于平面内,返回重合直线;
  • |den| <= angleTol|num| > tol:直线平行平面,无交;
  • 否则:
text 复制代码
t = -num / den
P = O + t D

对于有限线段,即使底层用无限直线公式生成候选点,最终也应回到端点有向距离和参数域做过滤。线段场景下,端点距离分类是更稳定、更直接的主流程。

三. 圆/椭圆与平面求交

适用对象:LGKCircle3d / LGKEllipse3d 与平面。

圆和椭圆都是位于自身平面内的完整平面曲线。与另一个平面求交时,数学上可以转化为"曲线所在平面与目标平面的交线"再与圆/椭圆求交;但是在几何内核中,直接把两平面交线作为主判据会有小角度容差问题。因此本方案优先使用圆/椭圆上点到目标平面的最大/最小有向距离进行分类。

1. 为什么不直接用两平面交线与圆/椭圆求交

设曲线所在平面为 Pi_c,目标平面为 Pi。当二者不平行时,精确数学交点一定落在:

text 复制代码
L = Pi_c ∩ Pi

因此直接计算 L,再做 L 与圆/椭圆求交,看起来最自然。但当两个平面夹角很小时,这个方案会变得不稳定

  1. 两平面交线的位置对平面法向和点位误差非常敏感。夹角越小,同样的平面距离误差会在交线位置上放大得越明显。
  2. L 与圆/椭圆的求交通常是严格的曲线内判定。如果交线因为数值误差被推离曲线,可能得到"无交"。
  3. 但从原始几何角度看,圆/椭圆上某个点到目标平面的距离可能已经在长度容差内,这应该被识别为容差接触或切触。
  4. 因此,小角度时"交线没有精确碰到圆/椭圆"不等价于"圆/椭圆与目标平面在容差意义下无交"。

换句话说,线-圆/线-椭圆适合作为交点生成手段,但不适合作为浅夹角情况下的唯一存在性判据。

更稳定的判据是直接考察曲线上所有点到目标平面的有向距离范围:

text 复制代码
dMin <= d(t) <= dMax

如果整个距离范围都在目标平面一侧,则无交;如果极值距离在容差内,则有一个切触/容差接触点;如果最小值和最大值异号,则有两个交点。这个判据直接作用在原始曲线和目标平面上,不需要构造病态的两平面交线。

2. 统一参数表达

椭圆参数式:

text 复制代码
E(t) = C + a cos(t) U + b sin(t) V

其中:

  • C:椭圆中心;
  • UV:椭圆所在平面内的两个正交单位轴;
  • ab:两个半轴长度;
  • N = U × V:椭圆所在平面法向;
  • t:椭圆参数,完整椭圆通常取 [0, 2pi)

圆是椭圆的特例:

text 复制代码
a = b = R

目标平面为:

text 复制代码
n · (X - P0) = 0

其中 n 是单位法向,P0 是平面上一点。

3. 先处理平面平行/共面

先判断曲线所在平面和目标平面是否平行:

text 复制代码
|N × n| <= angleTol

或等价地使用 abs(N · n) 接近 1

如果平行,则判断中心到目标平面的有向距离:

text 复制代码
D = n · (C - P0)
  • |D| <= tol:曲线整体位于目标平面内,返回重合类型,参数区间为完整圆/椭圆参数域,或弧段自身参数域;
  • |D| > tol:平行不共面,无交。

4. 非平行时用最大/最小有向距离分类

非平行时,曲线上点到目标平面的有向距离为:

text 复制代码
d(t) = n · (E(t) - P0)

它可以写成:

text 复制代码
d(t) = D + A cos(t) + B sin(t)

其中:

text 复制代码
D = n · (C - P0)
A = a (n · U)
B = b (n · V)

令:

text 复制代码
S = sqrt(A^2 + B^2)
phi = atan2(B, A)

则最大/最小有向距离和对应参数为:

text 复制代码
dMax = D + S,    tMax = phi
dMin = D - S,    tMin = phi + pi

对应空间点为:

text 复制代码
PMax = E(tMax)
PMin = E(tMin)

这一步对圆和椭圆都成立,不需要再按平面夹角是否大于 45° 分支。圆的最大/最小点退化为沿目标平面法向在圆平面内投影方向的两个点;椭圆的最大/最小点是椭圆在该方向上的支撑点,受半轴长度影响。

5. 交点数量分类

使用 dMindMax 和长度容差 tol 分类:

5.1 无交
text 复制代码
dMin > tol

或:

text 复制代码
dMax < -tol

说明整条圆/椭圆都在目标平面同一侧,无交。

5.2 一个切触/容差接触点

如果:

text 复制代码
|dMin| <= tol

则最小距离点 PMin = E(phi + pi) 是切触/容差接触点。

如果:

text 复制代码
|dMax| <= tol

则最大距离点 PMax = E(phi) 是切触/容差接触点。

这里推荐直接返回极值点,而不是再依赖两平面交线与圆/椭圆求交。因为这个分支正是浅夹角下最容易被交线法漏掉的情况。

5.3 两个交点

如果:

text 复制代码
dMin < -tol && dMax > tol

说明曲线一部分在目标平面正侧,一部分在负侧,必然有两个交点。

两个交点参数可以直接解析得到:

text 复制代码
theta = acos(clamp(-D / S, -1, 1))
t1 = phi + theta
t2 = phi - theta

再计算:

text 复制代码
P1 = E(t1)
P2 = E(t2)

候选点加入结果前仍需:

  1. 对周期参数做归一化;
  2. 如果是圆弧/椭圆弧,检查参数范围;
  3. 用点到目标平面的空间距离做最终验证;
  4. 按 3D 点距离去重;
  5. 根据切向与平面法向关系标记普通交点或切触点。

6. 容差模糊区处理

可能出现以下接近退化的情况:

  • S 非常小:非平行判断和距离函数矛盾,通常说明两个平面几乎平行、输入轴不规范或曲线退化;
  • dMindMax 都在容差内:数学上非平行完整非退化椭圆不应整体在平面内,但在容差意义下整条曲线可能都接近目标平面;
  • 两个解析交点非常接近:接近相切,应按空间距离去重,并优先返回切触类型。

这些情况不应通过宽泛异常或静默空结果掩盖。建议保留明确分支:要么按近似重合/容差接触返回,要么降级到通用数值校验,并在调试日志中暴露原因。

7. 伪代码

text 复制代码
IntersectEllipsePlane(E, Plane)
{
    C = E.center
    U = normalize(E.axisU)
    V = normalize(E.axisV)
    a = E.radiusU
    b = E.radiusV
    N = normalize(U × V)

    n = normalize(Plane.normal)
    P0 = Plane.origin

    if IsParallel(N, n, angleTol):
        D = n · (C - P0)
        if abs(D) <= tol:
            return Coincident(E, E.interval)
        return Empty

    D = n · (C - P0)
    A = a * (n · U)
    B = b * (n · V)
    S = hypot(A, B)

    if S <= tol:
        return HandleNearParallelOrDegenerateCase()

    phi = atan2(B, A)
    dMin = D - S
    dMax = D + S

    if dMin > tol or dMax < -tol:
        return Empty

    result = EmptyResult

    if abs(dMin) <= tol:
        t = NormalizePeriod(phi + pi)
        result.AddTouch(E(t), t)

    if abs(dMax) <= tol:
        t = NormalizePeriod(phi)
        result.AddTouch(E(t), t)

    if result not empty:
        result.UniqueBy3dDistance(tol)
        return result

    // dMin < -tol && dMax > tol
    q = clamp(-D / S, -1, 1)
    theta = acos(q)

    t1 = NormalizePeriod(phi + theta)
    t2 = NormalizePeriod(phi - theta)

    result.AddIfValid(E(t1), t1)
    result.AddIfValid(E(t2), t2)
    result.UniqueBy3dDistance(tol)
    return result
}

附录 A:椭圆到平面的最大/最小有向距离点推导

椭圆参数式为:

text 复制代码
E(t) = C + a cos(t) U + b sin(t) V

平面有向距离为:

text 复制代码
d(t) = n · (E(t) - P0)

代入椭圆参数式:

text 复制代码
d(t)
= n · (C - P0)
  + a (n · U) cos(t)
  + b (n · V) sin(t)

记:

text 复制代码
D = n · (C - P0)
A = a (n · U)
B = b (n · V)

则:

text 复制代码
d(t) = D + A cos(t) + B sin(t)

D 是常数,不影响极值点。只需要求:

text 复制代码
g(t) = A cos(t) + B sin(t)

的极值。对 g(t) 求导:

text 复制代码
g'(t) = -A sin(t) + B cos(t)

极值点满足:

text 复制代码
-A sin(t) + B cos(t) = 0

即:

text 复制代码
B cos(t) = A sin(t)

使用 atan2 避免 A = 0 的除零问题,得到:

text 复制代码
phi = atan2(B, A)

并有:

text 复制代码
cos(phi) = A / S
sin(phi) = B / S
S = sqrt(A^2 + B^2)

因此在 t = phi 时:

text 复制代码
g(phi) = A cos(phi) + B sin(phi)
       = A^2 / S + B^2 / S
       = S

这是最大值;在 t = phi + pi 时:

text 复制代码
g(phi + pi) = -S

这是最小值。所以:

text 复制代码
dMax = D + S,    tMax = phi
dMin = D - S,    tMin = phi + pi

几何上,最大点相对椭圆中心的向量为:

text 复制代码
E(tMax) - C
= a cos(phi) U + b sin(phi) V
= (a^2 (n · U) / S) U + (b^2 (n · V) / S) V

因此椭圆最大距离点是按 a^2b^2 加权的支撑点。圆的情况下 a = b = R,该方向退化为目标平面法向在圆平面内的投影方向。

附录 B:两个交点参数 t = phi ± acos(-D / S) 的推导

交点满足点在目标平面上:

text 复制代码
d(t) = 0

也就是:

text 复制代码
D + A cos(t) + B sin(t) = 0

令:

text 复制代码
S = sqrt(A^2 + B^2)
phi = atan2(B, A)

则:

text 复制代码
cos(phi) = A / S
sin(phi) = B / S

由余弦差角公式:

text 复制代码
cos(t - phi) = cos(t) cos(phi) + sin(t) sin(phi)

代入上式:

text 复制代码
cos(t - phi)
= cos(t) A / S + sin(t) B / S

两边乘以 S

text 复制代码
S cos(t - phi) = A cos(t) + B sin(t)

因此距离函数可以写为:

text 复制代码
d(t) = D + S cos(t - phi)

交点条件变成:

text 复制代码
D + S cos(t - phi) = 0

即:

text 复制代码
cos(t - phi) = -D / S

令:

text 复制代码
q = clamp(-D / S, -1, 1)
theta = acos(q)

余弦方程在一个周期内有两个解:

text 复制代码
t - phi =  theta
t - phi = -theta

所以两个交点参数为:

text 复制代码
t1 = phi + theta
t2 = phi - theta

也就是:

text 复制代码
t = phi ± acos(-D / S)

|D| = S 时,theta0pi,两个参数退化为同一个空间点,对应切触;实现上应由 dMin/dMax 的极值容差分支提前处理。