diff --git a/primitive_process/interface/subface/geometry/polyline_fillet.hpp b/primitive_process/interface/subface/geometry/polyline_fillet.hpp new file mode 100644 index 0000000..12a28a2 --- /dev/null +++ b/primitive_process/interface/subface/geometry/polyline_fillet.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +#include + +namespace internal +{ + +struct fillet_config_t { + // 圆角切除量占相邻两段中较短段弦长的比例(默认 5%) + // 越小则圆角越微小,棱角感越强。 + double segment_ratio = 0.05; + // 低于此角度(度)的衔接处视为共线,跳过不处理 + double min_turn_angle_deg = 0.5; +}; + +// 对 2D profile 多段线的衔接顶点插入微小圆角弧,实现 G1 连续。 +// +// 闭合多边形:处理全部 n 个顶点,输出隐式首尾相连。 +// 不闭合多段线:仅处理内部顶点 [1, n-2],首尾端点原样保留。 +// +// @param profile 原始 profile descriptor(通过 is_closed 区分开闭) +// @param out_points 输出点列表 +// @param out_bulges 输出 bulge 列表(与 out_points 等长) +// @param cfg 圆角参数 +void insert_profile_corner_fillets(const polyline_descriptor_t& profile, + std::vector& out_points, + std::vector& out_bulges, + const fillet_config_t& cfg = {}); + +} // namespace internal \ No newline at end of file diff --git a/primitive_process/src/subface/geometry/extrude_helixline_geometry.cpp b/primitive_process/src/subface/geometry/extrude_helixline_geometry.cpp index aa71026..d4d8de5 100644 --- a/primitive_process/src/subface/geometry/extrude_helixline_geometry.cpp +++ b/primitive_process/src/subface/geometry/extrude_helixline_geometry.cpp @@ -1,4 +1,5 @@ #include +#include namespace internal { @@ -13,19 +14,40 @@ void extrude_helixline_geometry_t::build() const auto& matrix_handle = this->axis_to_world.matrix(); const Eigen::Vector3d N = -matrix_handle.col(0); - const Eigen::Vector3d T = (matrix_handle.col(2) * this->axis_geom.height // + const Eigen::Vector3d T = (matrix_handle.col(2) * this->axis_geom.height + this->axis_geom.radius * this->axis_geom.total_theta * matrix_handle.col(1)) .normalized(); const Eigen::Vector3d B = N.cross(T); + // 对 profile 各衔接顶点插入微小圆角弧 + std::vector filleted_pts; + std::vector filleted_bgs; + const fillet_config_t fillet_cfg{/*segment_ratio=*/0.02, + /*min_turn_angle_deg=*/0.5}; + insert_profile_corner_fillets(profile_desc, filleted_pts, filleted_bgs, fillet_cfg); + + polyline_descriptor_t filleted_desc{}; + filleted_desc.point_number = static_cast(filleted_pts.size()); + filleted_desc.points = filleted_pts.data(); + filleted_desc.bulge_number = static_cast(filleted_bgs.size()); + filleted_desc.bulges = filleted_bgs.data(); + filleted_desc.reference_normal = profile_desc.reference_normal; + filleted_desc.is_close = profile_desc.is_close; + + std::cout << "[extrude_helixline_geometry_t::build] " + << "original profile points: " << profile_desc.point_number << ", after fillet: " << filleted_desc.point_number + << "\n"; + + // 构建 profile 几何数据(使用插入圆角后的 descriptor) aabb_t_dim<2> profile_aabb; - profile_geom.build_as_profile(profile_desc, B, N, matrix_handle.col(3).head<3>(), profile_aabb); + profile_geom.build_as_profile(filleted_desc, B, N, matrix_handle.col(3).head<3>(), profile_aabb); + std::cout << "[extrude_helixline_geometry_t::build] profile_aabb: min=" << profile_aabb.min().transpose() << ", max=" << profile_aabb.max().transpose() << std::endl; - const double profile_max_extent = std::max(profile_aabb.min().norm(), profile_aabb.max().norm()); - - helixline_aabb.min().array() -= profile_max_extent; - helixline_aabb.max().array() += profile_max_extent; + const double profile_max_extent = std::max(profile_aabb.min().norm(), profile_aabb.max().norm()); + helixline_aabb.min().array() -= profile_max_extent; + helixline_aabb.max().array() += profile_max_extent; } + } // namespace internal \ No newline at end of file diff --git a/primitive_process/src/subface/geometry/extrude_polyline_geometry.cpp b/primitive_process/src/subface/geometry/extrude_polyline_geometry.cpp index b7fcebd..81b3d02 100644 --- a/primitive_process/src/subface/geometry/extrude_polyline_geometry.cpp +++ b/primitive_process/src/subface/geometry/extrude_polyline_geometry.cpp @@ -1,4 +1,5 @@ #include +#include namespace internal { @@ -16,11 +17,34 @@ void extrude_polyline_geometry_t::build() const Eigen::Vector3d proj_y = axis_to_world.matrix().col(1).head<3>(); // Normal const Eigen::Vector3d origin = axis_to_world.matrix().col(3).head<3>(); - // 构建 profile 几何数据 + // 对 profile 各衔接顶点插入微小圆角弧 + // segment_ratio=0.05 意味着每段损失约 5%,棱角感仍然明显。 + // 如需更锋利,将 segment_ratio 调至 0.01-0.02; + // 如需更平滑,调至 0.1-0.2。 + std::vector filleted_pts; + std::vector filleted_bgs; + const fillet_config_t fillet_cfg{/*segment_ratio=*/0.05, + /*min_turn_angle_deg=*/0.5}; + insert_profile_corner_fillets(profile_desc, filleted_pts, filleted_bgs, fillet_cfg); + + // 构建临时 descriptor 指向圆角后的数据(不修改原始 descriptor) + polyline_descriptor_t filleted_desc{}; + filleted_desc.point_number = static_cast(filleted_pts.size()); + filleted_desc.points = filleted_pts.data(); + filleted_desc.bulge_number = static_cast(filleted_bgs.size()); + filleted_desc.bulges = filleted_bgs.data(); + filleted_desc.reference_normal = profile_desc.reference_normal; + filleted_desc.is_close = profile_desc.is_close; + + std::cout << "[extrude_polyline_geometry_t::build] " + << "original profile points: " << profile_desc.point_number << ", after fillet: " << filleted_desc.point_number + << "\n"; + + // 构建 profile 几何数据(使用插入圆角后的 descriptor) aabb_t_dim<2> profile_aabb; - profile_geom.build_as_profile(profile_desc, proj_x, proj_y, origin, profile_aabb); + profile_geom.build_as_profile(filleted_desc, proj_x, proj_y, origin, profile_aabb); - // 将 profile 的最大范围扩展到 polyline_aabb + // 将 profile 的最大范围扩展到 polyline_aabb const auto profile_max_extent = profile_aabb.max().cwiseMax(profile_aabb.min().cwiseAbs()).maxCoeff(); polyline_aabb.min().array() -= profile_max_extent; polyline_aabb.max().array() += profile_max_extent; diff --git a/primitive_process/src/subface/geometry/polyline_fillet.cpp b/primitive_process/src/subface/geometry/polyline_fillet.cpp new file mode 100644 index 0000000..0646d27 --- /dev/null +++ b/primitive_process/src/subface/geometry/polyline_fillet.cpp @@ -0,0 +1,215 @@ +#include +#include +//#include + +namespace internal +{ +namespace +{ + +// 将 2D 向量 v 旋转 angle 弧度(逆时针为正) +inline Eigen::Vector2d rotate2d(Eigen::Vector2d v, double angle) +{ + const double c = std::cos(angle), s = std::sin(angle); + return {c * v.x() - s * v.y(), s * v.x() + c * v.y()}; +} + +// 圆弧段 A→B(bulge b)在终点 B 处的切线方向 +Eigen::Vector2d arc_end_tangent(Eigen::Vector2d A, Eigen::Vector2d B, double bulge) +{ + Eigen::Vector2d d = B - A; + if (d.norm() < 1e-12) return Eigen::Vector2d::UnitX(); + d.normalize(); + if (std::abs(bulge) < 1e-12) return d; // 直线段 + + const double sign = bulge > 0 ? 1.0 : -1.0; + const double theta = 4.0 * std::atan(std::abs(bulge)); + return rotate2d(d, sign * theta / 2.0); +} + +// 圆弧段 A→B(bulge b)在起点 A 处的切线方向 +Eigen::Vector2d arc_start_tangent(Eigen::Vector2d A, Eigen::Vector2d B, double bulge) +{ + Eigen::Vector2d d = B - A; + if (d.norm() < 1e-12) return Eigen::Vector2d::UnitX(); + d.normalize(); + if (std::abs(bulge) < 1e-12) return d; + + const double sign = bulge > 0 ? 1.0 : -1.0; + const double theta = 4.0 * std::atan(std::abs(bulge)); + return rotate2d(d, -sign * theta / 2.0); +} + + +struct Corner { + bool active = false; + Eigen::Vector2d trim_in; // 圆弧起点 + Eigen::Vector2d trim_out; // 圆弧终点 + double fillet_bulge = 0; // 圆弧的 bulge +}; + +// 计算顶点 i 的圆角参数 +// @param pts 所有顶点 +// @param bulges 所有段的 bulge(bulges[i] 对应段 i→i+1,bulges[prev] 对应入射段) +// @param i 当前顶点索引 +// @param prev 入射段起点索引 +// @param next 出射段终点索引 +Corner compute_corner(const std::vector& pts, + const std::vector& bulges, + uint32_t i, + uint32_t prev, + uint32_t next, + const fillet_config_t& cfg) +{ + Corner c{}; + + const Eigen::Vector2d T_in = arc_end_tangent(pts[prev], pts[i], bulges[prev]); + const Eigen::Vector2d T_out = arc_start_tangent(pts[i], pts[next], bulges[i]); + + 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 min_turn = cfg.min_turn_angle_deg * (pi / 180.0); + if (turn_angle < min_turn) return c; // 视为共线,不处理 + + // 切除距离:取相邻两段弦长最小值的比例,并硬限幅 + const double chord_in = (pts[i] - pts[prev]).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}); + + if (trim < 1e-12) return c; + + c.trim_in = pts[i] - trim * T_in; + c.trim_out = pts[i] + trim * T_out; + + // bulge 符号:cross>0 → 左转(CCW多边形凸角) → CW弧 → 负 bulge + double fillet_bulge = std::tan(turn_angle / 4.0); + const double cross = T_in.x() * T_out.y() - T_in.y() * T_out.x(); + if (cross > 0.0) fillet_bulge = -fillet_bulge; + + c.active = true; + c.fillet_bulge = fillet_bulge; + return c; +} + + +inline void push_point(std::vector& out_points, + std::vector& out_bulges, + const Eigen::Vector2d& p, + double z, + double bulge) +{ + out_points.push_back({p.x(), p.y(), z}); + out_bulges.push_back(bulge); +} + +} // anonymous namespace + +void insert_profile_corner_fillets(const polyline_descriptor_t& profile, + std::vector& out_points, + std::vector& out_bulges, + const fillet_config_t& cfg) +{ + out_points.clear(); + out_bulges.clear(); + + const uint32_t n = profile.point_number; + const bool closed = profile.is_close; + + // ── 退化情况:点数过少,无内部衔接顶点可处理 ──────────────────────── + // 闭合:至少需要 3 点才有顶点(含衔接) + // 不闭合:至少需要 3 点才有内部顶点(至少 1 个 interior vertex) + if (n < 3) { + for (uint32_t i = 0; i < n; ++i) { + out_points.push_back(profile.points[i]); + out_bulges.push_back(profile.bulges ? profile.bulges[i] : 0.0); + } + return; + } + + std::vector pts(n); + std::vector bulges(n); + for (uint32_t i = 0; i < n; ++i) { + pts[i] = {profile.points[i].x, profile.points[i].y}; + bulges[i] = profile.bulges ? profile.bulges[i] : 0.0; + } + + // ── 逐顶点计算圆角 ──────────────────────────────────────────────────── + // 闭合:处理全部 n 个顶点(首尾环形相连) + // 不闭合:仅处理内部顶点 [1, n-2](首尾为端点,无入/出射配对) + std::vector corners(n); + + if (closed) { + for (uint32_t i = 0; i < n; ++i) { + const uint32_t prev = (i + n - 1) % n; + const uint32_t next = (i + 1) % n; + corners[i] = compute_corner(pts, bulges, i, prev, next, cfg); + } + } else { + // corners[0] 和 corners[n-1] 保持默认 inactive + for (uint32_t i = 1; i <= n - 2; ++i) { corners[i] = compute_corner(pts, bulges, i, i - 1, i + 1, cfg); } + } + + out_points.reserve(n * 2); + out_bulges.reserve(n * 2); + + if (closed) { + // 闭合:环形迭代,输出隐式首尾相连 + // + // 对每段 i(pts[i]→pts[next],bulge=bulges[i]): + // 1.段起点:corners[i].active ? trim_out[i] : pts[i] + // 2.若 corners[next].active:插入 trim_in[next](附 fillet_bulge[next]) + // 下一迭代自然从 trim_out[next] 开始 + for (uint32_t i = 0; i < n; ++i) { + const uint32_t next = (i + 1) % n; + const double z_i = profile.points[i].z; + const double z_nxt = profile.points[next].z; + + const Eigen::Vector2d& seg_start = corners[i].active ? corners[i].trim_out : pts[i]; + + push_point(out_points, out_bulges, seg_start, z_i, bulges[i]); + + if (corners[next].active) { + push_point(out_points, out_bulges, corners[next].trim_in, z_nxt, corners[next].fillet_bulge); + } + } + } else { + // 不闭合:线性迭代,明确保留首尾端点 + // + // 对每段 i(0..n-2),输出段起点;若 next 顶点有圆角(且非末点), + // 则额外插入 trim_in[next](附 fillet_bulge[next])作为本段实际终点, + // 下一迭代从 trim_out[next] 开始。 + + for (uint32_t i = 0; i < n - 1; ++i) { + const uint32_t next = i + 1; + const double z_i = profile.points[i].z; + const double z_nxt = profile.points[next].z; + + // 段起点: + // i==0 → 始终为 pts[0](端点,没有圆角) + // corners[i].active → trim_out[i](由上一迭代的圆角偏移得到) + // 否则 → pts[i] + const Eigen::Vector2d& seg_start = (i == 0) ? pts[0] : corners[i].active ? corners[i].trim_out : pts[i]; + + // 若 next 有圆角(且 next 不是末点,末点无圆角): + // 本段实际终点为 trim_in[next],之后插入圆弧, + // 下一迭代从 trim_out[next] 开始 + if (next < n - 1 && corners[next].active) { + push_point(out_points, out_bulges, seg_start, z_i, bulges[i]); + push_point(out_points, out_bulges, corners[next].trim_in, z_nxt, corners[next].fillet_bulge); + } else { + // next 无圆角(或 next 是末点):正常输出段起点 + push_point(out_points, out_bulges, seg_start, z_i, bulges[i]); + } + } + + // 末点:始终为 pts[n-1](端点,没有圆角) + // bulge 对最后一点无意义(无后续段),填 0.0 + out_points.push_back(profile.points[n - 1]); + out_bulges.push_back(0.0); + } + + assert(out_points.size() == out_bulges.size()); +} + +} // namespace internal \ No newline at end of file