|
|
@ -46,12 +46,51 @@ struct Corner { |
|
|
double fillet_bulge = 0; // 圆弧的 bulge
|
|
|
double fillet_bulge = 0; // 圆弧的 bulge
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 计算弧段 A→B(bulge b)的真实弧长,对直线段直接返回弦长。
|
|
|
|
|
|
static double seg_arc_len(const Eigen::Vector2d& A, const Eigen::Vector2d& B, double bulge) |
|
|
|
|
|
{ |
|
|
|
|
|
const double chord = (B - A).norm(); |
|
|
|
|
|
if (std::abs(bulge) < 1e-10) return chord; |
|
|
|
|
|
// 弧段包含角 theta_arc = 4·atan(|b|);弦与半径关系:chord = 2R·sin(theta_arc/2)
|
|
|
|
|
|
const double theta_arc = 4.0 * std::atan(std::abs(bulge)); |
|
|
|
|
|
// 避免 sin(theta_arc/2) → 0 的数值退化
|
|
|
|
|
|
const double sin_half = std::sin(theta_arc * 0.5); |
|
|
|
|
|
if (sin_half < 1e-15) return chord; |
|
|
|
|
|
return chord * theta_arc / (2.0 * sin_half); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static constexpr double kFilletDensity = 0.06; |
|
|
|
|
|
|
|
|
|
|
|
static double compute_per_corner_min_trim(double turn_angle, |
|
|
|
|
|
const Eigen::Vector2d& prev_pt, |
|
|
|
|
|
const Eigen::Vector2d& cur_pt, |
|
|
|
|
|
const Eigen::Vector2d& next_pt, |
|
|
|
|
|
double bulge_in, |
|
|
|
|
|
double bulge_out, |
|
|
|
|
|
double max_fraction) |
|
|
|
|
|
{ |
|
|
|
|
|
const double arc_len_in = seg_arc_len(prev_pt, cur_pt, bulge_in); |
|
|
|
|
|
const double arc_len_out = seg_arc_len(cur_pt, next_pt, bulge_out); |
|
|
|
|
|
const double min_len = std::min(arc_len_in, arc_len_out); |
|
|
|
|
|
|
|
|
|
|
|
// 曲率等化公式
|
|
|
|
|
|
const double cos_q = std::cos(turn_angle / 4.0); // cos(θ/4)
|
|
|
|
|
|
const double R_corner = 2.0 * kFilletDensity * cos_q * cos_q * min_len; |
|
|
|
|
|
const double trim_nat = R_corner * std::tan(turn_angle / 2.0); |
|
|
|
|
|
|
|
|
|
|
|
// 上限:trim 是直线裁切量,需与弦长比较
|
|
|
|
|
|
const double chord_in = (cur_pt - prev_pt).norm(); |
|
|
|
|
|
const double chord_out = (next_pt - cur_pt).norm(); |
|
|
|
|
|
return std::min({trim_nat, chord_in * max_fraction, chord_out * max_fraction}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 计算顶点 i 的圆角参数
|
|
|
// 计算顶点 i 的圆角参数
|
|
|
// @param pts 所有顶点
|
|
|
// @param pts 所有顶点(2D)
|
|
|
// @param bulges 所有段的 bulge(bulges[i] 对应段 i→i+1,bulges[prev] 对应入射段)
|
|
|
// @param bulges 所有段的 bulge(bulges[i] 对应段 i→i+1,bulges[prev] 对应入射段)
|
|
|
// @param i 当前顶点索引
|
|
|
// @param i 当前顶点索引
|
|
|
// @param prev 入射段起点索引
|
|
|
// @param prev 入射段起点索引
|
|
|
// @param next 出射段终点索引
|
|
|
// @param next 出射段终点索引
|
|
|
|
|
|
// @param cfg 圆角配置
|
|
|
Corner compute_corner(const std::vector<Eigen::Vector2d>& pts, |
|
|
Corner compute_corner(const std::vector<Eigen::Vector2d>& pts, |
|
|
const std::vector<double>& bulges, |
|
|
const std::vector<double>& bulges, |
|
|
uint32_t i, |
|
|
uint32_t i, |
|
|
@ -66,33 +105,52 @@ Corner compute_corner(const std::vector<Eigen::Vector2d>& pts, |
|
|
|
|
|
|
|
|
const double cos_a = std::clamp(T_in.dot(T_out), -1.0, 1.0); |
|
|
const double cos_a = std::clamp(T_in.dot(T_out), -1.0, 1.0); |
|
|
const double turn_angle = std::acos(cos_a); |
|
|
const double turn_angle = std::acos(cos_a); |
|
|
|
|
|
const double half_turn = turn_angle / 2.0; |
|
|
|
|
|
|
|
|
|
|
|
// 共线判断:转角过小,无需插入圆弧
|
|
|
const double min_turn = cfg.min_turn_angle_deg * (pi / 180.0); |
|
|
const double min_turn = cfg.min_turn_angle_deg * (pi / 180.0); |
|
|
if (turn_angle < min_turn) return c; // 视为共线,不处理
|
|
|
if (turn_angle < min_turn) return c; |
|
|
|
|
|
|
|
|
// 转角接近 180° 时,fillet 弧两端点几乎重合,弧半径趋近于零。
|
|
|
// 接近 180° 的折叠,圆弧几何退化,跳过
|
|
|
// 此时 fillet 无法在当前网格分辨率下被正确表达,应跳过以避免几何崩溃。
|
|
|
const double max_turn = cfg.max_fillet_turn_angle_deg * (pi / 180.0); |
|
|
const double max_fillet_turn_angle_deg = cfg.max_fillet_turn_angle_deg; |
|
|
if (turn_angle > max_turn) return c; |
|
|
if (turn_angle > max_fillet_turn_angle_deg * (pi / 180.0)) { |
|
|
|
|
|
std::cout << "转角 " << turn_angle * 180.0 / pi << "° 超过阈值,跳过圆角(避免退化弧)" << std::endl; |
|
|
double trim = 0.0; |
|
|
return c; |
|
|
if (cfg.auto_mode) { |
|
|
} |
|
|
// 依据局部转角和相邻弧段长度计算最小圆弧,
|
|
|
|
|
|
trim = compute_per_corner_min_trim(turn_angle, |
|
|
|
|
|
pts[prev], |
|
|
|
|
|
pts[i], |
|
|
|
|
|
pts[next], |
|
|
|
|
|
bulges[prev], |
|
|
|
|
|
bulges[i], |
|
|
|
|
|
cfg.max_segment_fraction); |
|
|
|
|
|
if (trim < 1e-15) return c; // 近似共线,跳过
|
|
|
|
|
|
|
|
|
|
|
|
} else if (cfg.absolute_fillet_radius > 0.0) { |
|
|
|
|
|
// 绝对半径
|
|
|
|
|
|
const double chord_in = (pts[i] - pts[prev]).norm(); |
|
|
|
|
|
const double chord_out = (pts[next] - pts[i]).norm(); |
|
|
|
|
|
const double tan_half = std::tan(half_turn); |
|
|
|
|
|
trim = cfg.absolute_fillet_radius * tan_half; |
|
|
|
|
|
trim = std::min({trim, chord_in * 0.1, chord_out * 0.1}); |
|
|
|
|
|
if (trim < 1e-15) return c; |
|
|
|
|
|
|
|
|
const double chord_in = (pts[i] - pts[prev]).norm(); |
|
|
} else { |
|
|
const double chord_out = (pts[next] - pts[i]).norm(); |
|
|
// 相对比例
|
|
|
const double trim = std::min({std::min(chord_in, chord_out) * cfg.segment_ratio, chord_in * 0.45, chord_out * 0.45}); |
|
|
const double chord_in = (pts[i] - pts[prev]).norm(); |
|
|
|
|
|
const double chord_out = (pts[next] - pts[i]).norm(); |
|
|
// 计算实际 fillet 圆弧半径并与最小可分辨阈值比较
|
|
|
trim = std::min({std::min(chord_in, chord_out) * cfg.segment_ratio, chord_in * 0.1, chord_out * 0.1}); |
|
|
// R_fillet = trim / tan(turn_angle/2)
|
|
|
if (trim < 1e-15) return c; |
|
|
const double half_turn = turn_angle / 2.0; |
|
|
|
|
|
const double R_fillet = (std::tan(half_turn) > 1e-9) ? trim / std::tan(half_turn) : 0.0; |
|
|
|
|
|
if (R_fillet < cfg.min_fillet_radius) { |
|
|
|
|
|
std::cout << "圆角半径 " << R_fillet << " 低于最小阈值 " << cfg.min_fillet_radius << ",跳过圆角" << std::endl; |
|
|
|
|
|
return c; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (trim < 1e-12) return c; |
|
|
// 额外检查半径下限
|
|
|
|
|
|
if (!cfg.auto_mode) { |
|
|
|
|
|
const double R_fillet = (std::tan(half_turn) > 1e-15) ? trim / std::tan(half_turn) : 0.0; |
|
|
|
|
|
if (R_fillet < cfg.min_fillet_radius) return c; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 计算圆弧起终点及 bulge
|
|
|
c.trim_in = pts[i] - trim * T_in; |
|
|
c.trim_in = pts[i] - trim * T_in; |
|
|
c.trim_out = pts[i] + trim * T_out; |
|
|
c.trim_out = pts[i] + trim * T_out; |
|
|
|
|
|
|
|
|
@ -108,9 +166,6 @@ Corner compute_corner(const std::vector<Eigen::Vector2d>& pts, |
|
|
/**
|
|
|
/**
|
|
|
* @brief 将圆弧 A→B(bulge b)裁切为子弧 A_new→B_new 后,重新计算子弧的 bulge。 |
|
|
* @brief 将圆弧 A→B(bulge b)裁切为子弧 A_new→B_new 后,重新计算子弧的 bulge。 |
|
|
* |
|
|
* |
|
|
* Bulge 依赖弦长和包含角,裁切端点后两者均变化,必须重算。 |
|
|
|
|
|
* 对直线段(b≈0)直接返回 0,不做计算。 |
|
|
|
|
|
* |
|
|
|
|
|
* @param A 原弧起点 |
|
|
* @param A 原弧起点 |
|
|
* @param B 原弧终点 |
|
|
* @param B 原弧终点 |
|
|
* @param b 原弧 bulge |
|
|
* @param b 原弧 bulge |
|
|
@ -124,46 +179,46 @@ static double recompute_subarc_bulge(const Eigen::Vector2d& A, |
|
|
const Eigen::Vector2d& A_new, |
|
|
const Eigen::Vector2d& A_new, |
|
|
const Eigen::Vector2d& B_new) |
|
|
const Eigen::Vector2d& B_new) |
|
|
{ |
|
|
{ |
|
|
// 直线段:裁切后仍是直线
|
|
|
|
|
|
if (std::abs(b) < 1e-10) return 0.0; |
|
|
if (std::abs(b) < 1e-10) return 0.0; |
|
|
|
|
|
|
|
|
// 原弧包含角 θ = 4·atan(|b|),半角 = 2·atan(|b|)
|
|
|
|
|
|
const double half_theta = 2.0 * std::atan(std::abs(b)); |
|
|
const double half_theta = 2.0 * std::atan(std::abs(b)); |
|
|
const double sign_b = (b > 0.0) ? 1.0 : -1.0; |
|
|
const double sign_b = (b > 0.0) ? 1.0 : -1.0; |
|
|
|
|
|
|
|
|
// 原弧弦
|
|
|
|
|
|
const Eigen::Vector2d chord = B - A; |
|
|
const Eigen::Vector2d chord = B - A; |
|
|
const double chord_len = chord.norm(); |
|
|
const double chord_len = chord.norm(); |
|
|
if (chord_len < 1e-12) return b; |
|
|
if (chord_len < 1e-12) return b; |
|
|
|
|
|
|
|
|
// 圆半径与圆心
|
|
|
|
|
|
const double R = chord_len / (2.0 * std::sin(half_theta)); |
|
|
const double R = chord_len / (2.0 * std::sin(half_theta)); |
|
|
// 弦中垂线方向(左手正交),对 CCW 弧(b>0)圆心在弦左侧
|
|
|
|
|
|
const Eigen::Vector2d perp_unit(-chord.y() / chord_len, chord.x() / chord_len); |
|
|
const Eigen::Vector2d perp_unit(-chord.y() / chord_len, chord.x() / chord_len); |
|
|
const double d = R * std::cos(half_theta); // 弦中点到圆心的距离
|
|
|
const double d = R * std::cos(half_theta); |
|
|
const Eigen::Vector2d center = 0.5 * (A + B) + sign_b * d * perp_unit; |
|
|
const Eigen::Vector2d center = 0.5 * (A + B) + sign_b * d * perp_unit; |
|
|
|
|
|
|
|
|
// 新起终点在圆上的角度
|
|
|
// 用向量叉积/点积计算子弧张角
|
|
|
const double ang_A = std::atan2(A_new.y() - center.y(), A_new.x() - center.x()); |
|
|
const Eigen::Vector2d rA = A_new - center; |
|
|
const double ang_B = std::atan2(B_new.y() - center.y(), B_new.x() - center.x()); |
|
|
const Eigen::Vector2d rB = B_new - center; |
|
|
|
|
|
|
|
|
double delta = ang_B - ang_A; |
|
|
const double ang_A = std::atan2(rA.y(), rA.x()); |
|
|
|
|
|
const double ang_B = std::atan2(rB.y(), rB.x()); |
|
|
|
|
|
double delta = ang_B - ang_A; |
|
|
|
|
|
|
|
|
// 规范化:子弧行进方向与原弧一致(CCW 为正,CW 为负)
|
|
|
// 确保 delta 符号与 b 一致,且 |delta| <= 2π
|
|
|
if (sign_b > 0.0) { |
|
|
if (sign_b > 0.0) { |
|
|
// 强制 delta 落入 (0, 2π]
|
|
|
// CCW:delta 应落入 (0, 2π]
|
|
|
delta = std::fmod(delta, 2.0 * pi); |
|
|
while (delta <= 0.0) delta += 2.0 * pi; |
|
|
if (delta <= 0.0) delta += 2.0 * pi; |
|
|
while (delta > 2.0 * pi) delta -= 2.0 * pi; |
|
|
} else { |
|
|
} else { |
|
|
// 强制 delta 落入 [-2π, 0)
|
|
|
// CW:delta 应落入 [-2π, 0)
|
|
|
delta = std::fmod(delta, 2.0 * pi); |
|
|
while (delta >= 0.0) delta -= 2.0 * pi; |
|
|
if (delta >= 0.0) delta -= 2.0 * pi; |
|
|
while (delta < -2.0 * pi) delta += 2.0 * pi; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
double result = std::tan(delta / 4.0); |
|
|
if (std::abs(delta) < 1e-13) { |
|
|
if ((b > 0) != (result > 0) && std::abs(result) > 1e-9) { |
|
|
const double rA_len = rA.norm(); |
|
|
std::cerr << "[BULGE_SIGN_FLIP] original_b=" << b << " recomputed_bulge=" << result << " delta=" << delta |
|
|
const double rB_len = rB.norm(); |
|
|
<< " ang_A=" << ang_A << " ang_B=" << ang_B << "\n"; |
|
|
if (rA_len < 1e-15 || rB_len < 1e-15) return 0.0; |
|
|
|
|
|
const double full_theta = 4.0 * std::atan(std::abs(b)); |
|
|
|
|
|
const double ratio = std::abs(delta) / full_theta; |
|
|
|
|
|
return sign_b * std::abs(b) * ratio; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return std::tan(delta / 4.0); |
|
|
return std::tan(delta / 4.0); |
|
|
@ -181,8 +236,6 @@ inline void push_point(std::vector<vector3d>& out_points, |
|
|
|
|
|
|
|
|
/**
|
|
|
/**
|
|
|
* @brief 计算点 p 到线段 [a, b] 的最短距离(2D)。 |
|
|
* @brief 计算点 p 到线段 [a, b] 的最短距离(2D)。 |
|
|
* @details 将参数 t 限制在 [0,1] 以保证只测量到线段本身,而非无限延长线, |
|
|
|
|
|
* 从而避免把圆弧圆心到相邻线段延长线的误距离当作相交依据。 |
|
|
|
|
|
*/ |
|
|
*/ |
|
|
static double point_to_segment_dist_2d(const Eigen::Vector2d& p, const Eigen::Vector2d& a, const Eigen::Vector2d& b) |
|
|
static double point_to_segment_dist_2d(const Eigen::Vector2d& p, const Eigen::Vector2d& a, const Eigen::Vector2d& b) |
|
|
{ |
|
|
{ |
|
|
@ -193,6 +246,57 @@ static double point_to_segment_dist_2d(const Eigen::Vector2d& p, const Eigen::Ve |
|
|
return (p - (a + t * ab)).norm(); |
|
|
return (p - (a + t * ab)).norm(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 计算点 p 到圆弧段(起点A, 终点B, bulge b)的最短距离
|
|
|
|
|
|
static double point_to_arc_dist_2d(const Eigen::Vector2d& p, const Eigen::Vector2d& A, const Eigen::Vector2d& B, double bulge) |
|
|
|
|
|
{ |
|
|
|
|
|
if (std::abs(bulge) < 1e-9) { return point_to_segment_dist_2d(p, A, B); } |
|
|
|
|
|
|
|
|
|
|
|
const double half_theta = 2.0 * std::atan(std::abs(bulge)); |
|
|
|
|
|
const double chord_len = (B - A).norm(); |
|
|
|
|
|
if (chord_len < 1e-12) return (p - A).norm(); |
|
|
|
|
|
|
|
|
|
|
|
const double R = chord_len / (2.0 * std::sin(half_theta)); |
|
|
|
|
|
const Eigen::Vector2d chord = B - A; |
|
|
|
|
|
const double sign_b = (bulge > 0.0) ? 1.0 : -1.0; |
|
|
|
|
|
const Eigen::Vector2d perp(-chord.y() / chord_len, chord.x() / chord_len); |
|
|
|
|
|
const double d_center = R * std::cos(half_theta); |
|
|
|
|
|
const Eigen::Vector2d C = 0.5 * (A + B) + sign_b * d_center * perp; |
|
|
|
|
|
|
|
|
|
|
|
const Eigen::Vector2d cp = p - C; |
|
|
|
|
|
const double cp_len = cp.norm(); |
|
|
|
|
|
|
|
|
|
|
|
const double ang_A = std::atan2((A - C).y(), (A - C).x()); |
|
|
|
|
|
const double ang_B = std::atan2((B - C).y(), (B - C).x()); |
|
|
|
|
|
const double ang_p = std::atan2(cp.y(), cp.x()); |
|
|
|
|
|
|
|
|
|
|
|
auto angle_in_arc = [&]() -> bool { |
|
|
|
|
|
double span = ang_B - ang_A; |
|
|
|
|
|
if (sign_b > 0.0) { |
|
|
|
|
|
while (span <= 0.0) span += 2.0 * pi; |
|
|
|
|
|
while (span > 2.0 * pi) span -= 2.0 * pi; |
|
|
|
|
|
} else { |
|
|
|
|
|
while (span >= 0.0) span -= 2.0 * pi; |
|
|
|
|
|
while (span < -2.0 * pi) span += 2.0 * pi; |
|
|
|
|
|
} |
|
|
|
|
|
double rel = ang_p - ang_A; |
|
|
|
|
|
if (sign_b > 0.0) { |
|
|
|
|
|
while (rel < 0.0) rel += 2.0 * pi; |
|
|
|
|
|
while (rel > 2.0 * pi) rel -= 2.0 * pi; |
|
|
|
|
|
return rel <= std::abs(span); |
|
|
|
|
|
} else { |
|
|
|
|
|
while (rel > 0.0) rel -= 2.0 * pi; |
|
|
|
|
|
while (rel < -2.0 * pi) rel += 2.0 * pi; |
|
|
|
|
|
return rel >= span; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if (cp_len > 1e-15 && angle_in_arc()) { |
|
|
|
|
|
return std::abs(cp_len - R); |
|
|
|
|
|
} else { |
|
|
|
|
|
return std::min((p - A).norm(), (p - B).norm()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} // anonymous namespace
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
|
/**
|
|
|
/**
|
|
|
@ -200,18 +304,10 @@ static double point_to_segment_dist_2d(const Eigen::Vector2d& p, const Eigen::Ve |
|
|
* |
|
|
* |
|
|
* @details |
|
|
* @details |
|
|
* 对于负 bulge(CW 弧,在 CCW profile 中表现为内凹弧),若其圆心到相邻线段的 |
|
|
* 对于负 bulge(CW 弧,在 CCW profile 中表现为内凹弧),若其圆心到相邻线段的 |
|
|
* 距离 < 圆弧半径 R,则该弧与相邻边相交。自相交区域内,PMC 的最近点查询会产生 |
|
|
* 距离 < 圆弧半径 R,则该弧与相邻边相交。修复策略:将发生自相交的段的 bulge |
|
|
* 歧义,导致 SDF 符号局部翻转,进而在错误位置生成多余等值面(即图中异常轮廓)。 |
|
|
* 置 0,退化为直线段。 |
|
|
* |
|
|
|
|
|
* 修复策略:将发生自相交的段的 bulge 置 0,退化为直线段,并输出诊断日志。 |
|
|
|
|
|
* |
|
|
* |
|
|
* 几何原理(以 CW 弧为例): |
|
|
* @param pts_3d polyline 顶点 |
|
|
* 弦 AB,半角 half_theta = 2*atan(|b|) |
|
|
|
|
|
* R = |AB| / (2 * sin(half_theta)) |
|
|
|
|
|
* 圆心 C = midpoint(A,B) + R*cos(half_theta) * perp_right(AB) |
|
|
|
|
|
* 若 dist(C, 相邻段) < R → 圆与相邻段相交 → 弧自相交 |
|
|
|
|
|
* |
|
|
|
|
|
* @param pts_3d polyline 顶点(3D,原地读取,不修改) |
|
|
|
|
|
* @param bulges 各段 bulge 值(若检测到自相交则原地置 0) |
|
|
* @param bulges 各段 bulge 值(若检测到自相交则原地置 0) |
|
|
* @param is_axis true = XZ 平面(Axis),false = XY 平面(Profile) |
|
|
* @param is_axis true = XZ 平面(Axis),false = XY 平面(Profile) |
|
|
* @param closed 是否为闭合多段线 |
|
|
* @param closed 是否为闭合多段线 |
|
|
@ -245,42 +341,31 @@ void sanitize_negative_bulge_self_intersections(const std::vector<vector3d>& pts |
|
|
const double chord_len = (B - A).norm(); |
|
|
const double chord_len = (B - A).norm(); |
|
|
if (chord_len < 1e-12) continue; |
|
|
if (chord_len < 1e-12) continue; |
|
|
|
|
|
|
|
|
// 计算 CW 弧的圆心与半径
|
|
|
|
|
|
// half_theta = theta/2,其中 theta = 4*atan(|b|) 为弧所对圆心角
|
|
|
|
|
|
const double abs_b = std::abs(b); |
|
|
const double abs_b = std::abs(b); |
|
|
const double half_theta = 2.0 * std::atan(abs_b); |
|
|
const double half_theta = 2.0 * std::atan(abs_b); |
|
|
const double R = chord_len / (2.0 * std::sin(half_theta)); |
|
|
const double R = chord_len / (2.0 * std::sin(half_theta)); |
|
|
const Eigen::Vector2d chord_dir = (B - A) / chord_len; |
|
|
const Eigen::Vector2d chord_dir = (B - A) / chord_len; |
|
|
// CW 弧(b < 0)的圆心在弦 AB 的右侧
|
|
|
|
|
|
const Eigen::Vector2d perp_right = {chord_dir.y(), -chord_dir.x()}; |
|
|
const Eigen::Vector2d perp_right = {chord_dir.y(), -chord_dir.x()}; |
|
|
const double d_center = R * std::cos(half_theta); // 弦中点到圆心的距离
|
|
|
const double d_center = R * std::cos(half_theta); |
|
|
const Eigen::Vector2d center = 0.5 * (A + B) + d_center * perp_right; |
|
|
const Eigen::Vector2d center = 0.5 * (A + B) + d_center * perp_right; |
|
|
|
|
|
|
|
|
bool self_intersects = false; |
|
|
bool self_intersects = false; |
|
|
|
|
|
|
|
|
// ---- 检测与前一段 [pts[i-1], A] 的自相交 ----
|
|
|
// 检测与前一段 [pts[i-1], A] 的自相交
|
|
|
if (closed || i > 0u) { |
|
|
if (closed || i > 0u) { |
|
|
const uint32_t i0 = (i + n - 1u) % n; |
|
|
const uint32_t i0 = (i + n - 1u) % n; |
|
|
const double dist = point_to_segment_dist_2d(center, pts[i0], A); |
|
|
const double dist = point_to_segment_dist_2d(center, pts[i0], A); |
|
|
if (dist < R * (1.0 - 1e-6)) { |
|
|
if (dist < R * (1.0 - 1e-6)) { |
|
|
self_intersects = true; |
|
|
self_intersects = true; |
|
|
std::cout << "[SANITIZE_ARC] 段[" << i << "] 负bulge圆弧" |
|
|
|
|
|
<< "(R=" << R << ", bulge=" << b << ")" |
|
|
|
|
|
<< "圆心到前段距离=" << dist << " < R," |
|
|
|
|
|
<< "检测到与前段自相交,bulge → 0(退化直线)\n"; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// ---- 检测与后一段 [B, pts[i+2]] 的自相交 ----
|
|
|
// 检测与后一段 [B, pts[i+2]] 的自相交
|
|
|
if (!self_intersects && (closed || i + 2u < n)) { |
|
|
if (!self_intersects && (closed || i + 2u < n)) { |
|
|
const uint32_t i2 = (i + 2u) % n; |
|
|
const uint32_t i2 = (i + 2u) % n; |
|
|
const double dist = point_to_segment_dist_2d(center, B, pts[i2]); |
|
|
const double dist = point_to_segment_dist_2d(center, B, pts[i2]); |
|
|
if (dist < R * (1.0 - 1e-6)) { |
|
|
if (dist < R * (1.0 - 1e-6)) { |
|
|
self_intersects = true; |
|
|
self_intersects = true; |
|
|
std::cout << "[SANITIZE_ARC] 段[" << i << "] 负bulge圆弧" |
|
|
|
|
|
<< "(R=" << R << ", bulge=" << b << ")" |
|
|
|
|
|
<< "圆心到后段距离=" << dist << " < R," |
|
|
|
|
|
<< "检测到与后段自相交,bulge → 0(退化直线)\n"; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -304,17 +389,15 @@ void insert_polyline_corner_fillets(const polyline_descriptor_t& desc, |
|
|
|
|
|
|
|
|
if (cfg.is_axis) { |
|
|
if (cfg.is_axis) { |
|
|
// axis:XZ 平面
|
|
|
// axis:XZ 平面
|
|
|
to_2d = [](const vector3d& p) -> Eigen::Vector2d { return {p.z, p.x}; }; // 3D -> 2D: (Z, X)
|
|
|
to_2d = [](const vector3d& p) -> Eigen::Vector2d { return {p.z, p.x}; }; |
|
|
to_3d = [](const Eigen::Vector2d& q, double fixed_y) -> vector3d { |
|
|
to_3d = [](const Eigen::Vector2d& q, double fixed_y) -> vector3d { return {q.y(), fixed_y, q.x()}; }; |
|
|
return {q.y(), fixed_y, q.x()}; |
|
|
|
|
|
}; // 2D -> 3D: (X, Y, Z) = (q.y, fixed_y, q.x)
|
|
|
|
|
|
} else { |
|
|
} else { |
|
|
// profile:XY 平面
|
|
|
// profile:XY 平面
|
|
|
to_2d = [](const vector3d& p) -> Eigen::Vector2d { return {p.x, p.y}; }; |
|
|
to_2d = [](const vector3d& p) -> Eigen::Vector2d { return {p.x, p.y}; }; |
|
|
to_3d = [](const Eigen::Vector2d& q, double fixed_z) -> vector3d { return {q.x(), q.y(), fixed_z}; }; |
|
|
to_3d = [](const Eigen::Vector2d& q, double fixed_z) -> vector3d { return {q.x(), q.y(), fixed_z}; }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 退化情况
|
|
|
// 退化情况:顶点数 < 3,无法形成转角
|
|
|
if (n < 3) { |
|
|
if (n < 3) { |
|
|
for (uint32_t i = 0; i < n; ++i) { |
|
|
for (uint32_t i = 0; i < n; ++i) { |
|
|
out_points.push_back(desc.points[i]); |
|
|
out_points.push_back(desc.points[i]); |
|
|
@ -325,21 +408,20 @@ void insert_polyline_corner_fillets(const polyline_descriptor_t& desc, |
|
|
|
|
|
|
|
|
std::vector<Eigen::Vector2d> pts(n); |
|
|
std::vector<Eigen::Vector2d> pts(n); |
|
|
std::vector<double> bulges(n); |
|
|
std::vector<double> bulges(n); |
|
|
// fixed_coord:axis→y分量(恒=0),profile→z分量(恒=0)
|
|
|
|
|
|
std::vector<double> fixed_coords(n); |
|
|
std::vector<double> fixed_coords(n); |
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < n; ++i) { |
|
|
for (uint32_t i = 0; i < n; ++i) { |
|
|
pts[i] = to_2d(desc.points[i]); |
|
|
pts[i] = to_2d(desc.points[i]); |
|
|
bulges[i] = desc.bulges ? desc.bulges[i] : 0.0; |
|
|
bulges[i] = desc.bulges ? desc.bulges[i] : 0.0; |
|
|
fixed_coords[i] = cfg.is_axis ? desc.points[i].y // axis:保留 y(=0)
|
|
|
fixed_coords[i] = cfg.is_axis ? desc.points[i].y : desc.points[i].z; |
|
|
: desc.points[i].z; // profile:保留 z(=0)
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 计算每个顶点的圆角
|
|
|
// 计算每个顶点的圆角参数
|
|
|
std::vector<Corner> corners(n); |
|
|
std::vector<Corner> corners(n); |
|
|
if (closed) { |
|
|
if (closed) { |
|
|
for (uint32_t i = 0; i < n; ++i) corners[i] = compute_corner(pts, bulges, i, (i + n - 1) % n, (i + 1) % n, cfg); |
|
|
for (uint32_t i = 0; i < n; ++i) corners[i] = compute_corner(pts, bulges, i, (i + n - 1) % n, (i + 1) % n, cfg); |
|
|
} else { |
|
|
} else { |
|
|
|
|
|
// 开放曲线:首尾两端无转角
|
|
|
for (uint32_t i = 1; i <= n - 2; ++i) corners[i] = compute_corner(pts, bulges, i, i - 1, i + 1, cfg); |
|
|
for (uint32_t i = 1; i <= n - 2; ++i) corners[i] = compute_corner(pts, bulges, i, i - 1, i + 1, cfg); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -357,10 +439,10 @@ void insert_polyline_corner_fillets(const polyline_descriptor_t& desc, |
|
|
const Eigen::Vector2d seg_start = corners[i].active ? corners[i].trim_out : pts[i]; |
|
|
const Eigen::Vector2d seg_start = corners[i].active ? corners[i].trim_out : pts[i]; |
|
|
const Eigen::Vector2d seg_end = corners[next].active ? corners[next].trim_in : pts[next]; |
|
|
const Eigen::Vector2d seg_end = corners[next].active ? corners[next].trim_in : pts[next]; |
|
|
|
|
|
|
|
|
// 子弧端点已改变,必须重算 bulge
|
|
|
// 重算 bulge
|
|
|
const double seg_bulge = recompute_subarc_bulge(pts[i], pts[next], bulges[i], seg_start, seg_end); |
|
|
const double seg_bulge = recompute_subarc_bulge(pts[i], pts[next], bulges[i], seg_start, seg_end); |
|
|
|
|
|
|
|
|
push_pt(seg_start, fixed_coords[i], seg_bulge); // 原来是 bulges[i]
|
|
|
push_pt(seg_start, fixed_coords[i], seg_bulge); |
|
|
if (corners[next].active) push_pt(corners[next].trim_in, fixed_coords[next], corners[next].fillet_bulge); |
|
|
if (corners[next].active) push_pt(corners[next].trim_in, fixed_coords[next], corners[next].fillet_bulge); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
@ -370,7 +452,6 @@ void insert_polyline_corner_fillets(const polyline_descriptor_t& desc, |
|
|
const Eigen::Vector2d seg_start = (i == 0) ? pts[0] : (corners[i].active ? corners[i].trim_out : pts[i]); |
|
|
const Eigen::Vector2d seg_start = (i == 0) ? pts[0] : (corners[i].active ? corners[i].trim_out : pts[i]); |
|
|
const Eigen::Vector2d seg_end = (next < n - 1 && corners[next].active) ? corners[next].trim_in : pts[next]; |
|
|
const Eigen::Vector2d seg_end = (next < n - 1 && corners[next].active) ? corners[next].trim_in : pts[next]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const double seg_bulge = recompute_subarc_bulge(pts[i], pts[next], bulges[i], seg_start, seg_end); |
|
|
const double seg_bulge = recompute_subarc_bulge(pts[i], pts[next], bulges[i], seg_start, seg_end); |
|
|
|
|
|
|
|
|
push_pt(seg_start, fixed_coords[i], seg_bulge); |
|
|
push_pt(seg_start, fixed_coords[i], seg_bulge); |
|
|
|