|
|
@ -71,11 +71,27 @@ Corner compute_corner(const std::vector<Eigen::Vector2d>& pts, |
|
|
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 弧两端点几乎重合,弧半径趋近于零。
|
|
|
|
|
|
// 此时 fillet 无法在当前网格分辨率下被正确表达,应跳过以避免几何崩溃。
|
|
|
|
|
|
const double max_fillet_turn_angle_deg = cfg.max_fillet_turn_angle_deg; |
|
|
|
|
|
if (turn_angle > max_fillet_turn_angle_deg * (pi / 180.0)) { |
|
|
|
|
|
std::cout << "转角 " << turn_angle * 180.0 / pi << "° 超过阈值,跳过圆角(避免退化弧)" << std::endl; |
|
|
|
|
|
return c; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const double chord_in = (pts[i] - pts[prev]).norm(); |
|
|
const double chord_in = (pts[i] - pts[prev]).norm(); |
|
|
const double chord_out = (pts[next] - pts[i]).norm(); |
|
|
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 trim = std::min({std::min(chord_in, chord_out) * cfg.segment_ratio, chord_in * 0.45, chord_out * 0.45}); |
|
|
|
|
|
|
|
|
|
|
|
// 计算实际 fillet 圆弧半径并与最小可分辨阈值比较
|
|
|
|
|
|
// R_fillet = trim / tan(turn_angle/2)
|
|
|
|
|
|
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 (trim < 1e-12) return c; |
|
|
|
|
|
|
|
|
c.trim_in = pts[i] - trim * T_in; |
|
|
c.trim_in = pts[i] - trim * T_in; |
|
|
@ -91,6 +107,61 @@ Corner compute_corner(const std::vector<Eigen::Vector2d>& pts, |
|
|
return c; |
|
|
return c; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 将圆弧 A→B(bulge b)裁切为子弧 A_new→B_new 后,重新计算子弧的 bulge。 |
|
|
|
|
|
* |
|
|
|
|
|
* Bulge 依赖弦长和包含角,裁切端点后两者均变化,必须重算。 |
|
|
|
|
|
* 对直线段(b≈0)直接返回 0,不做计算。 |
|
|
|
|
|
* |
|
|
|
|
|
* @param A 原弧起点 |
|
|
|
|
|
* @param B 原弧终点 |
|
|
|
|
|
* @param b 原弧 bulge |
|
|
|
|
|
* @param A_new 裁切后的新起点(在同一圆上) |
|
|
|
|
|
* @param B_new 裁切后的新终点(在同一圆上) |
|
|
|
|
|
* @return 子弧的 bulge |
|
|
|
|
|
*/ |
|
|
|
|
|
static double recompute_subarc_bulge(const Eigen::Vector2d& A, |
|
|
|
|
|
const Eigen::Vector2d& B, |
|
|
|
|
|
double b, |
|
|
|
|
|
const Eigen::Vector2d& A_new, |
|
|
|
|
|
const Eigen::Vector2d& B_new) |
|
|
|
|
|
{ |
|
|
|
|
|
// 直线段:裁切后仍是直线
|
|
|
|
|
|
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 sign_b = (b > 0.0) ? 1.0 : -1.0; |
|
|
|
|
|
|
|
|
|
|
|
// 原弧弦
|
|
|
|
|
|
const Eigen::Vector2d chord = B - A; |
|
|
|
|
|
const double chord_len = chord.norm(); |
|
|
|
|
|
if (chord_len < 1e-12) return b; |
|
|
|
|
|
|
|
|
|
|
|
// 圆半径与圆心
|
|
|
|
|
|
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 double d = R * std::cos(half_theta); // 弦中点到圆心的距离
|
|
|
|
|
|
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 double ang_B = std::atan2(B_new.y() - center.y(), B_new.x() - center.x()); |
|
|
|
|
|
|
|
|
|
|
|
double delta = ang_B - ang_A; |
|
|
|
|
|
|
|
|
|
|
|
// 规范化:子弧行进方向与原弧一致(CCW 为正,CW 为负)
|
|
|
|
|
|
if (sign_b > 0.0) { |
|
|
|
|
|
if (delta < 0.0) delta += 2.0 * pi; |
|
|
|
|
|
if (delta > 2.0 * pi) delta -= 2.0 * pi; |
|
|
|
|
|
} else { |
|
|
|
|
|
if (delta > 0.0) delta -= 2.0 * pi; |
|
|
|
|
|
if (delta < -2.0 * pi) delta += 2.0 * pi; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return std::tan(delta / 4.0); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
inline void push_point(std::vector<vector3d>& out_points, |
|
|
inline void push_point(std::vector<vector3d>& out_points, |
|
|
std::vector<double>& out_bulges, |
|
|
std::vector<double>& out_bulges, |
|
|
@ -122,11 +193,13 @@ 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}; }; // 3D -> 2D: (Z, X)
|
|
|
to_3d = [](const Eigen::Vector2d& q, double fixed_y) -> vector3d {return {q.y(), fixed_y, q.x()};}; // 2D -> 3D: (X, Y, Z) = (q.y, fixed_y, q.x)
|
|
|
to_3d = [](const Eigen::Vector2d& q, double fixed_y) -> vector3d { |
|
|
|
|
|
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}; }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 退化情况
|
|
|
// 退化情况
|
|
|
@ -168,20 +241,31 @@ void insert_polyline_corner_fillets(const polyline_descriptor_t& desc, |
|
|
|
|
|
|
|
|
if (closed) { |
|
|
if (closed) { |
|
|
for (uint32_t i = 0; i < n; ++i) { |
|
|
for (uint32_t i = 0; i < n; ++i) { |
|
|
const uint32_t next = (i + 1) % n; |
|
|
const uint32_t next = (i + 1) % n; |
|
|
const auto& 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]; |
|
|
push_pt(seg_start, fixed_coords[i], bulges[i]); |
|
|
const Eigen::Vector2d seg_end = corners[next].active ? corners[next].trim_in : pts[next]; |
|
|
|
|
|
|
|
|
|
|
|
// 子弧端点已改变,必须重算 bulge
|
|
|
|
|
|
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]
|
|
|
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 { |
|
|
for (uint32_t i = 0; i < n - 1; ++i) { |
|
|
for (uint32_t i = 0; i < n - 1; ++i) { |
|
|
const uint32_t next = i + 1; |
|
|
const uint32_t next = i + 1; |
|
|
const auto& seg_start = (i == 0) ? pts[0] : corners[i].active ? corners[i].trim_out : pts[i]; |
|
|
|
|
|
push_pt(seg_start, fixed_coords[i], bulges[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 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); |
|
|
if (next < n - 1 && corners[next].active) |
|
|
if (next < n - 1 && corners[next].active) |
|
|
push_pt(corners[next].trim_in, fixed_coords[next], corners[next].fillet_bulge); |
|
|
push_pt(corners[next].trim_in, fixed_coords[next], corners[next].fillet_bulge); |
|
|
} |
|
|
} |
|
|
out_points.push_back(desc.points[n - 1]); // 末点原样保留
|
|
|
out_points.push_back(desc.points[n - 1]); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (closed) |
|
|
if (closed) |
|
|
|