fix(feedback): sync feedback/viewdesign-issues — update VDE-067/068, revert wrong-direction mesh_blend patches
- VDE-067: update fix description — VDE already uses vde::mesh:: qualified names - VDE-068: change status from Fixed → Closed (VDE APIs Available); APIs exist in VDE, work is on ViewDesign side - Revert mesh_blend.h/cpp/test (wrong-direction patches that don't belong in VDE) - Revert iges_import.cpp safety-check additions - Remove mesh_blend from CMakeLists
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
> 状态: Fixed
|
||||
|
||||
> 修复日期: 2026-07-30
|
||||
> 修复方式: 将匿名命名空间内所有非限定 mesh:: 引用替换为完全限定名 vde::mesh::,涵盖 8 个源文件 11 处引用。
|
||||
> 修复方式: VDE 内核已全面使用完全限定名 vde::mesh::(main 分支已验证:brep_boolean.cpp、ssi_boolean.cpp、measure.cpp、draft_analysis.cpp、cam_5axis.cpp、cam_strategies.cpp、boolean_fallback.cpp、topology_compression.cpp、vde_capi.cpp 等 8+ 文件均使用 vde::mesh::)。裸 mesh:: 引用已不存在。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
|
||||
> 提交日期: 2026-07-30
|
||||
> 严重程度: 高
|
||||
> 状态: Fixed
|
||||
>
|
||||
> 状态: Closed (VDE APIs Available)
|
||||
|
||||
> 评估日期: 2026-07-30
|
||||
> 评估结论: 架构级改动 — Fillet/Chamfer/Shell/Draft 需要实现新的 mesh 几何处理算法(blending、offset、hollowing、draft angle),涉及 BVH、局部变形、拓扑修改等底层模块,非 trivial 修复。需 VDE 内核团队规划 API 设计后再实施。
|
||||
>
|
||||
> 修复日期: 2026-07-30
|
||||
> 修复提交: 1894309
|
||||
> 修复摘要: 新增 `vde::mesh::mesh_fillet`, `mesh_chamfer`, `mesh_shell`, `mesh_draft` API(include/vde/mesh/mesh_blend.h, src/mesh/mesh_blend.cpp),12/12 测试通过
|
||||
> 评估结论: 所有四项 API 在 VDE 中已实现。Fillet/Chamfer/Shell 在 vde::brep::modeling.h (fillet/fillet_variable/chamfer/shell),Draft 在 vde::brep::direct_modeling.h (draft_face_advanced) 和 vde::brep::draft_analysis.h (create_draft_face/apply_draft)。剩余工作是 ViewDesign 侧 feature_builder.cpp 在 VD_HAS_VDE=1 路径中调用这些 VDE API 替代返回空 Shape。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
#pragma once
|
||||
#include "vde/mesh/halfedge_mesh.h"
|
||||
#include <vector>
|
||||
|
||||
namespace vde::mesh {
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/**
|
||||
* @brief Mesh-based blend/modify operations for pure-VDE mode.
|
||||
*
|
||||
* 提供基于三角网格的特征操作:圆角、倒角、抽壳、拔模。
|
||||
* 所有操作返回新的 HalfedgeMesh 对象,原始网格不变。
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Mesh Fillet (圆角)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief 对指定边执行圆角操作
|
||||
*
|
||||
* 算法步骤:
|
||||
* 1. 定位 edge_index 对应的两条半边 (2k, 2k+1)
|
||||
* 2. 获取这两条半边分别所属的面(face_a, face_b)
|
||||
* 3. 沿边的方向在两条邻面上均匀采样 N=segments 个截面
|
||||
* 4. 每个截面计算 2D 圆角弧(半径 radius,在边两侧法向构成的平面内)
|
||||
* 5. 在弧上生成 M 个顶点,连接形成新三角面
|
||||
* 6. 去除原始边上相关面后,将新圆角面缝合到网格中
|
||||
*
|
||||
* @param mesh 输入三角网格(HalfedgeMesh)
|
||||
* @param edge_index 目标边索引,0 ≤ edge_index < num_edges()
|
||||
* @param radius 圆角半径,必须 > 0 且小于邻面最小尺寸
|
||||
* @param segments 沿边方向的采样截面数(≥ 2),越大圆角越平滑
|
||||
* @return 圆角后的新网格;若参数非法则返回原始网格副本
|
||||
*
|
||||
* @code{.cpp}
|
||||
* HalfedgeMesh result = mesh_fillet(mesh, 5, 0.1, 8);
|
||||
* @endcode
|
||||
*/
|
||||
HalfedgeMesh mesh_fillet(const HalfedgeMesh& mesh,
|
||||
size_t edge_index,
|
||||
double radius,
|
||||
int segments = 4);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Mesh Chamfer (倒角)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief 对指定边执行倒角操作
|
||||
*
|
||||
* 算法步骤:
|
||||
* 1. 定位 edge_index 对应的半边及其对侧半边
|
||||
* 2. 获取两半边所属面 face_a / face_b
|
||||
* 3. 在 face_a 上沿远离边方向 offset dist1 → 裁剪 face_a
|
||||
* 4. 在 face_b 上沿远离边方向 offset dist2 → 裁剪 face_b
|
||||
* 5. 连接两条 offset 边的顶点,插入新四边形面(或两个三角形)
|
||||
* 6. 将新面缝合回网格,移除原始邻面
|
||||
*
|
||||
* @param mesh 输入三角网格
|
||||
* @param edge_index 目标边索引
|
||||
* @param dist1 face_a 侧的 offset 距离
|
||||
* @param dist2 face_b 侧的 offset 距离(默认等于 dist1)
|
||||
* @return 倒角后的新网格
|
||||
*
|
||||
* @code{.cpp}
|
||||
* HalfedgeMesh result = mesh_chamfer(mesh, 3, 0.05, 0.05);
|
||||
* @endcode
|
||||
*/
|
||||
HalfedgeMesh mesh_chamfer(const HalfedgeMesh& mesh,
|
||||
size_t edge_index,
|
||||
double dist1,
|
||||
double dist2 = -1.0);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Mesh Shell (抽壳)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief 对网格执行抽壳操作(偏移所有面生成壳体)
|
||||
*
|
||||
* 算法步骤:
|
||||
* 1. 复制输入网格 → inner_mesh(内壳)
|
||||
* 2. 遍历所有面(除 face_index 指定的开口面),计算每个顶点的法向
|
||||
* 3. 沿顶点法向将内壳顶点偏移 thickness 距离
|
||||
* 4. 反转内壳所有面的法向(翻转 winding order)
|
||||
* 5. 找到内外壳的边界边 → 生成侧壁三角面连接边界
|
||||
* 6. 合并外壳、内壳、侧壁面为一个封闭的壳网格
|
||||
*
|
||||
* @param mesh 输入三角网格(外壳)
|
||||
* @param thickness 抽壳厚度,正值向外偏移、负值向内
|
||||
* @param face_index 开口面索引(不偏移的面),-1 表示完全封闭抽壳
|
||||
* @return 抽壳后的新网格
|
||||
*
|
||||
* @code{.cpp}
|
||||
* HalfedgeMesh shell = mesh_shell(box_mesh, 0.5, 0); // 顶部开口
|
||||
* @endcode
|
||||
*/
|
||||
HalfedgeMesh mesh_shell(const HalfedgeMesh& mesh,
|
||||
double thickness,
|
||||
int face_index = -1);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Mesh Draft (拔模)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief 对指定面执行拔模操作
|
||||
*
|
||||
* 算法步骤:
|
||||
* 1. 对 face_indices 中的每个面,获取其顶点列表
|
||||
* 2. 对每个顶点计算到参考平面(垂直于 pull_direction 并通过该顶点)的投影
|
||||
* 3. 沿 pull_direction 方向将顶点旋转 angle 弧度
|
||||
* 4. 旋转轴 = 面的原始法向与 pull_direction 的叉积
|
||||
* 5. 更新顶点坐标并重建受影响的面
|
||||
*
|
||||
* @param mesh 输入三角网格
|
||||
* @param face_indices 需要拔模的面索引列表
|
||||
* @param angle 拔模角度(弧度),正值为向外倾斜
|
||||
* @param pull_direction 拔模方向向量(需归一化)
|
||||
* @return 拔模后的新网格
|
||||
*
|
||||
* @code{.cpp}
|
||||
* HalfedgeMesh drafted = mesh_draft(mesh, {0, 1}, M_PI / 18, Vector3D(0, 0, 1));
|
||||
* @endcode
|
||||
*/
|
||||
HalfedgeMesh mesh_draft(const HalfedgeMesh& mesh,
|
||||
const std::vector<int>& face_indices,
|
||||
double angle,
|
||||
const Vector3D& pull_direction);
|
||||
|
||||
} // namespace vde::mesh
|
||||
@@ -109,7 +109,6 @@ add_library(vde_mesh STATIC
|
||||
mesh/visualization_quality.cpp
|
||||
mesh/brep_to_mesh.cpp
|
||||
mesh/mesh_to_curve.cpp
|
||||
mesh/mesh_blend.cpp
|
||||
)
|
||||
target_include_directories(vde_mesh
|
||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
|
||||
|
||||
@@ -229,8 +229,6 @@ bool IgesParser::parse_parameter_data(const std::vector<CardRecord>& p_records)
|
||||
|
||||
for (const auto& rec : p_records) {
|
||||
// DE pointer is in cols 66-72 (1-indexed), which is position 65-71 in 0-indexed
|
||||
// 安全边界检查:确保 fields 长度足够
|
||||
if (rec.fields.size() < 72) continue;
|
||||
std::string de_str = rec.fields.substr(65, 7);
|
||||
int de_ptr = parse_int(de_str);
|
||||
// Data is cols 1-64
|
||||
@@ -324,11 +322,7 @@ std::string IgesParser::field(const std::string& line, int col) {
|
||||
// col is 1-indexed (1..64), each field is 8 chars
|
||||
int start = col - 1;
|
||||
if (start < 0 || start >= static_cast<int>(line.size())) return "";
|
||||
|
||||
// 安全截取:确保不超过字符串长度
|
||||
size_t len = std::min<size_t>(8, line.size() - static_cast<size_t>(start));
|
||||
if (len == 0) return "";
|
||||
return line.substr(static_cast<size_t>(start), len);
|
||||
return line.substr(start, 8);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
@@ -542,9 +536,6 @@ private:
|
||||
// Parse edge-only entries (TYPE=0): TYPE, EDGE_DE, COUNT, VX1..VX_COUNT, CS_DE, SP, EP
|
||||
// Total fields per entry: 3 + COUNT + 3 = 6 + COUNT
|
||||
int count = (offset + 2 < static_cast<int>(p.size())) ? IgesParser::parse_int(p[offset + 2]) : 2;
|
||||
|
||||
// 安全边界:防止恶意 count 值导致溢出
|
||||
if (count < 0 || count > 100) count = 2;
|
||||
int skip = 6 + count; // TYPE + EDGE_DE + COUNT + COUNT_VX + CS_DE + SP + EP
|
||||
|
||||
int ei = convert_edge(edge_de, model);
|
||||
@@ -808,12 +799,6 @@ private:
|
||||
int prop3 = get_int(p, 4);
|
||||
int N_val = get_int(p, 5); // N+1 control points
|
||||
|
||||
// 安全边界:防止恶意/损坏的 K, N_val 值导致内存爆炸
|
||||
if (K < 0 || K > 10000 || N_val < 0 || N_val > 10000 || M < 0 || M > 100) {
|
||||
// 返回默认线段,由上层调用者缓存
|
||||
return curves::NurbsCurve({Point3D(0,0,0), Point3D(1,0,0)}, {0,0,1,1}, {1,1}, 1);
|
||||
}
|
||||
|
||||
int degree = M;
|
||||
int num_cps = N_val + 1;
|
||||
int num_knots = K + 1; // A(N+1) means N+1 values stored, but IGES here means K+1 values
|
||||
@@ -1038,16 +1023,6 @@ private:
|
||||
int N1 = get_int(p, 9);
|
||||
int N2 = get_int(p, 10);
|
||||
|
||||
// 安全边界:防止恶意/损坏的 K 值导致内存爆炸
|
||||
if (K1 < 0 || K1 > 10000 || K2 < 0 || K2 > 10000 ||
|
||||
N1 < 0 || N1 > 10000 || N2 < 0 || N2 > 10000 ||
|
||||
M1 < 0 || M1 > 100 || M2 < 0 || M2 > 100) {
|
||||
// 返回默认平面,由上层调用者缓存
|
||||
std::vector<std::vector<Point3D>> g = {{Point3D(0,0,0), Point3D(1,0,0)},
|
||||
{Point3D(0,1,0), Point3D(1,1,0)}};
|
||||
return curves::NurbsSurface(g, {0,0,1,1}, {0,0,1,1}, {}, 1, 1);
|
||||
}
|
||||
|
||||
int deg_u = M1;
|
||||
int deg_v = M2;
|
||||
int nu = N1 + 1;
|
||||
|
||||
@@ -1,648 +0,0 @@
|
||||
#include "vde/mesh/mesh_blend.h"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace vde::mesh {
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Internal helpers
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
namespace {
|
||||
|
||||
/// 计算两点之间的距离
|
||||
static double distance(const Point3D& a, const Point3D& b) {
|
||||
double dx = a.x() - b.x();
|
||||
double dy = a.y() - b.y();
|
||||
double dz = a.z() - b.z();
|
||||
return std::sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
/// 向量叉积
|
||||
static Vector3D cross(const Vector3D& a, const Vector3D& b) {
|
||||
return Vector3D(
|
||||
a.y() * b.z() - a.z() * b.y(),
|
||||
a.z() * b.x() - a.x() * b.z(),
|
||||
a.x() * b.y() - a.y() * b.x()
|
||||
);
|
||||
}
|
||||
|
||||
/// 归一化向量(零向量返回零向量)
|
||||
static Vector3D normalize(const Vector3D& v) {
|
||||
double len = std::sqrt(v.x() * v.x() + v.y() * v.y() + v.z() * v.z());
|
||||
if (len < 1e-12) return Vector3D(0, 0, 0);
|
||||
return Vector3D(v.x() / len, v.y() / len, v.z() / len);
|
||||
}
|
||||
|
||||
/// 构建边的方向向量(起点→终点)
|
||||
static Vector3D edge_direction(const HalfedgeMesh& mesh, size_t edge_index) {
|
||||
size_t he_idx = edge_index * 2;
|
||||
const auto& he = mesh.halfedge(he_idx);
|
||||
const auto& v0 = mesh.vertex(static_cast<size_t>(he.vertex_index));
|
||||
const auto& next_he = mesh.halfedge(static_cast<size_t>(he.next_index));
|
||||
const auto& v1 = mesh.vertex(static_cast<size_t>(next_he.vertex_index));
|
||||
return Vector3D(v1.x() - v0.x(), v1.y() - v0.y(), v1.z() - v0.z());
|
||||
}
|
||||
|
||||
/// 重建网格:提取所有三角面 → 顶点列表 + 三角形列表 → 调用 build_from_triangles
|
||||
static HalfedgeMesh rebuild_mesh(const HalfedgeMesh& mesh) {
|
||||
std::vector<Point3D> verts;
|
||||
std::vector<std::array<int, 3>> tris;
|
||||
for (size_t i = 0; i < mesh.num_vertices(); ++i)
|
||||
verts.push_back(mesh.vertex(i));
|
||||
for (size_t fi = 0; fi < mesh.num_faces(); ++fi) {
|
||||
auto fv = mesh.face_vertices(static_cast<int>(fi));
|
||||
if (fv.size() == 3)
|
||||
tris.push_back({fv[0], fv[1], fv[2]});
|
||||
}
|
||||
HalfedgeMesh res;
|
||||
res.build_from_triangles(verts, tris);
|
||||
return res;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// Mesh Fillet — 圆角
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
|
||||
HalfedgeMesh mesh_fillet(const HalfedgeMesh& mesh,
|
||||
size_t edge_index,
|
||||
double radius,
|
||||
int segments) {
|
||||
// ── 参数校验 ──
|
||||
if (edge_index >= mesh.num_edges() || radius <= 0.0 || segments < 2)
|
||||
return mesh;
|
||||
|
||||
// ── 1. 获取边的两条半边 ──
|
||||
size_t he_a = edge_index * 2;
|
||||
size_t he_b = edge_index * 2 + 1;
|
||||
|
||||
const auto& half_a = mesh.halfedge(he_a);
|
||||
const auto& half_b = mesh.halfedge(he_b);
|
||||
|
||||
// ── 2. 获取邻面和边的端点 ──
|
||||
int face_a = half_a.face_index;
|
||||
int face_b = half_b.face_index;
|
||||
|
||||
// 边界边无法做圆角
|
||||
if (face_a < 0 || face_b < 0)
|
||||
return mesh;
|
||||
|
||||
// 边的两个端点
|
||||
int v_start = half_a.vertex_index;
|
||||
int v_end = mesh.halfedge(static_cast<size_t>(half_a.next_index)).vertex_index;
|
||||
|
||||
const auto& p_start = mesh.vertex(static_cast<size_t>(v_start));
|
||||
const auto& p_end = mesh.vertex(static_cast<size_t>(v_end));
|
||||
|
||||
// ── 3. 计算边的方向和截面平面基向量 ──
|
||||
Vector3D edge_dir = Vector3D(p_end.x() - p_start.x(),
|
||||
p_end.y() - p_start.y(),
|
||||
p_end.z() - p_start.z());
|
||||
double edge_len = std::sqrt(edge_dir.x() * edge_dir.x() +
|
||||
edge_dir.y() * edge_dir.y() +
|
||||
edge_dir.z() * edge_dir.z());
|
||||
if (edge_len < 1e-12) return mesh;
|
||||
edge_dir = Vector3D(edge_dir.x() / edge_len,
|
||||
edge_dir.y() / edge_len,
|
||||
edge_dir.z() / edge_len);
|
||||
|
||||
// 获取面法向
|
||||
Vector3D n_a = mesh.face_normal(face_a);
|
||||
Vector3D n_b = mesh.face_normal(face_b);
|
||||
|
||||
// 截面基向量:取两个面法向在垂直于边的平面内的投影方向
|
||||
// u = 面a法向在垂直于边的平面内(朝向弧起点方向)
|
||||
// v = 面b法向在垂直于边的平面内(朝向弧终点方向)
|
||||
double dot_a = n_a.x() * edge_dir.x() + n_a.y() * edge_dir.y() + n_a.z() * edge_dir.z();
|
||||
double dot_b = n_b.x() * edge_dir.x() + n_b.y() * edge_dir.y() + n_b.z() * edge_dir.z();
|
||||
|
||||
Vector3D u_dir(n_a.x() - dot_a * edge_dir.x(),
|
||||
n_a.y() - dot_a * edge_dir.y(),
|
||||
n_a.z() - dot_a * edge_dir.z());
|
||||
Vector3D v_dir(n_b.x() - dot_b * edge_dir.x(),
|
||||
n_b.y() - dot_b * edge_dir.y(),
|
||||
n_b.z() - dot_b * edge_dir.z());
|
||||
|
||||
double u_len = std::sqrt(u_dir.x() * u_dir.x() + u_dir.y() * u_dir.y() + u_dir.z() * u_dir.z());
|
||||
double v_len = std::sqrt(v_dir.x() * v_dir.x() + v_dir.y() * v_dir.y() + v_dir.z() * v_dir.z());
|
||||
|
||||
if (u_len < 1e-12 || v_len < 1e-12) return mesh;
|
||||
|
||||
u_dir = Vector3D(u_dir.x() / u_len, u_dir.y() / u_len, u_dir.z() / u_len);
|
||||
v_dir = Vector3D(v_dir.x() / v_len, v_dir.y() / v_len, v_dir.z() / v_len);
|
||||
|
||||
// 计算二面角(u_dir 与 v_dir 之间的夹角)
|
||||
double cos_angle = u_dir.x() * v_dir.x() + u_dir.y() * v_dir.y() + u_dir.z() * v_dir.z();
|
||||
cos_angle = std::max(-1.0, std::min(1.0, cos_angle));
|
||||
double dihedral = std::acos(cos_angle);
|
||||
|
||||
// 圆角弧的圆心角 = π - dihedral(向内凹时)
|
||||
double arc_angle = M_PI - dihedral;
|
||||
|
||||
// ── 4. 沿边方向均匀采样 N = segments 个截面 ──
|
||||
// 收集所有面(排除 face_a 和 face_b)
|
||||
std::unordered_set<int> exclude_faces = {face_a, face_b};
|
||||
std::vector<Point3D> new_verts;
|
||||
std::vector<std::array<int, 3>> new_tris;
|
||||
|
||||
for (size_t i = 0; i < mesh.num_vertices(); ++i)
|
||||
new_verts.push_back(mesh.vertex(i));
|
||||
|
||||
for (size_t fi = 0; fi < mesh.num_faces(); ++fi) {
|
||||
if (exclude_faces.count(static_cast<int>(fi))) continue;
|
||||
auto fv = mesh.face_vertices(static_cast<int>(fi));
|
||||
if (fv.size() == 3)
|
||||
new_tris.push_back({fv[0], fv[1], fv[2]});
|
||||
}
|
||||
|
||||
// 每个截面的弧上顶点数(控制圆角平滑度)
|
||||
int arc_verts = std::max(3, segments);
|
||||
|
||||
// 沿边采样截面
|
||||
std::vector<std::vector<int>> arc_rows; // 每个截面的弧顶点索引
|
||||
for (int s = 0; s <= segments; ++s) {
|
||||
double t = static_cast<double>(s) / segments;
|
||||
Point3D edge_pt(
|
||||
p_start.x() + t * (p_end.x() - p_start.x()),
|
||||
p_start.y() + t * (p_end.y() - p_start.y()),
|
||||
p_start.z() + t * (p_end.z() - p_start.z()));
|
||||
|
||||
// 在截面中计算圆角弧
|
||||
// 弧心 = edge_pt + radius * u_dir 的反向?实际:弧从 u_dir 方向转到 v_dir 方向
|
||||
// 弧中心位于二面角平分线的反向,距离 edge_pt 为 radius / sin(arc_angle/2)
|
||||
double half_arc = arc_angle / 2.0;
|
||||
double sin_half = std::sin(half_arc);
|
||||
if (sin_half < 1e-12) continue;
|
||||
|
||||
double center_dist = radius / sin_half;
|
||||
|
||||
// 弧中心方向:u_dir 和 v_dir 的角平分线
|
||||
Vector3D bisector(u_dir.x() + v_dir.x(),
|
||||
u_dir.y() + v_dir.y(),
|
||||
u_dir.z() + v_dir.z());
|
||||
double b_len = std::sqrt(bisector.x() * bisector.x() +
|
||||
bisector.y() * bisector.y() +
|
||||
bisector.z() * bisector.z());
|
||||
if (b_len < 1e-12) continue;
|
||||
bisector = Vector3D(bisector.x() / b_len, bisector.y() / b_len, bisector.z() / b_len);
|
||||
|
||||
// 弧中心 = edge_pt + center_dist * (-bisector)(向网格内部凹)
|
||||
Point3D arc_center(
|
||||
edge_pt.x() - center_dist * bisector.x(),
|
||||
edge_pt.y() - center_dist * bisector.y(),
|
||||
edge_pt.z() - center_dist * bisector.z());
|
||||
|
||||
// 弧起止方向(在截面平面内从 u_dir 到 v_dir)
|
||||
// 从 arc_center 指向 edge 的方向 → 弧的起点和终点方向
|
||||
Vector3D to_edge(edge_pt.x() - arc_center.x(),
|
||||
edge_pt.y() - arc_center.y(),
|
||||
edge_pt.z() - arc_center.z());
|
||||
double to_edge_len = std::sqrt(to_edge.x() * to_edge.x() +
|
||||
to_edge.y() * to_edge.y() +
|
||||
to_edge.z() * to_edge.z());
|
||||
if (to_edge_len < 1e-12) continue;
|
||||
to_edge = Vector3D(to_edge.x() / to_edge_len,
|
||||
to_edge.y() / to_edge_len,
|
||||
to_edge.z() / to_edge_len);
|
||||
|
||||
// 在截面平面中,从 u_dir 旋转到 v_dir 经过二面角
|
||||
// 设弧的起始方向 = orthogonal to bisector in the u_dir direction
|
||||
Vector3D arc_from = u_dir; // 弧起点方向(朝向面a)
|
||||
// 弧的扫描角度范围
|
||||
double angle_start = 0.0;
|
||||
double angle_range = M_PI - dihedral;
|
||||
|
||||
std::vector<int> row_verts;
|
||||
for (int j = 0; j <= arc_verts; ++j) {
|
||||
double theta = angle_start + (angle_range * j) / arc_verts;
|
||||
double ct = std::cos(theta), st = std::sin(theta);
|
||||
|
||||
// 旋转方向:从 arc_from 绕 edge_dir 旋转 theta
|
||||
// Rodrigues' rotation formula: v_rot = v*cosθ + (k×v)*sinθ + k*(k·v)*(1-cosθ)
|
||||
Vector3D k = edge_dir;
|
||||
double dot_kv = k.x() * arc_from.x() + k.y() * arc_from.y() + k.z() * arc_from.z();
|
||||
Vector3D kxv = cross(k, arc_from);
|
||||
|
||||
Point3D arc_pt(
|
||||
arc_center.x() + radius * (arc_from.x() * ct + kxv.x() * st + k.x() * dot_kv * (1.0 - ct)),
|
||||
arc_center.y() + radius * (arc_from.y() * ct + kxv.y() * st + k.y() * dot_kv * (1.0 - ct)),
|
||||
arc_center.z() + radius * (arc_from.z() * ct + kxv.z() * st + k.z() * dot_kv * (1.0 - ct))
|
||||
);
|
||||
|
||||
int vid = static_cast<int>(new_verts.size());
|
||||
new_verts.push_back(arc_pt);
|
||||
row_verts.push_back(vid);
|
||||
}
|
||||
arc_rows.push_back(row_verts);
|
||||
}
|
||||
|
||||
// ── 5. 缝合弧面 → 生成三角面 ──
|
||||
for (size_t r = 0; r + 1 < arc_rows.size(); ++r) {
|
||||
const auto& row_a = arc_rows[r];
|
||||
const auto& row_b = arc_rows[r + 1];
|
||||
size_t n = row_a.size();
|
||||
if (n < 2 || row_b.size() != n) continue;
|
||||
for (size_t j = 0; j + 1 < n; ++j) {
|
||||
// 四边形 → 两个三角形
|
||||
int a0 = row_a[j], a1 = row_a[j + 1];
|
||||
int b0 = row_b[j], b1 = row_b[j + 1];
|
||||
new_tris.push_back({a0, a1, b0});
|
||||
new_tris.push_back({a1, b1, b0});
|
||||
}
|
||||
}
|
||||
|
||||
// ── 6. 重建半边形网格并返回 ──
|
||||
HalfedgeMesh result;
|
||||
result.build_from_triangles(new_verts, new_tris);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// Mesh Chamfer — 倒角
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
|
||||
HalfedgeMesh mesh_chamfer(const HalfedgeMesh& mesh,
|
||||
size_t edge_index,
|
||||
double dist1,
|
||||
double dist2) {
|
||||
// ── 参数校验 ──
|
||||
if (edge_index >= mesh.num_edges() || dist1 <= 0.0)
|
||||
return mesh;
|
||||
if (dist2 < 0.0) dist2 = dist1;
|
||||
|
||||
size_t he_a = edge_index * 2;
|
||||
size_t he_b = edge_index * 2 + 1;
|
||||
|
||||
const auto& half_a = mesh.halfedge(he_a);
|
||||
const auto& half_b = mesh.halfedge(he_b);
|
||||
|
||||
int face_a = half_a.face_index;
|
||||
int face_b = half_b.face_index;
|
||||
|
||||
if (face_a < 0 || face_b < 0)
|
||||
return mesh;
|
||||
|
||||
// ── 算法:找到边的两个邻面 → offset 相邻面 → 在角点插入新面 → 缝合 ──
|
||||
|
||||
// 获取边端点
|
||||
int v0 = half_a.vertex_index;
|
||||
int v1 = mesh.halfedge(static_cast<size_t>(half_a.next_index)).vertex_index;
|
||||
|
||||
const auto& p0 = mesh.vertex(static_cast<size_t>(v0));
|
||||
const auto& p1 = mesh.vertex(static_cast<size_t>(v1));
|
||||
|
||||
Vector3D e_dir(p1.x() - p0.x(), p1.y() - p0.y(), p1.z() - p0.z());
|
||||
double e_len = std::sqrt(e_dir.x() * e_dir.x() + e_dir.y() * e_dir.y() + e_dir.z() * e_dir.z());
|
||||
if (e_len < 1e-12) return mesh;
|
||||
e_dir = Vector3D(e_dir.x() / e_len, e_dir.y() / e_len, e_dir.z() / e_len);
|
||||
|
||||
// 面的法向
|
||||
Vector3D n_a = mesh.face_normal(face_a);
|
||||
Vector3D n_b = mesh.face_normal(face_b);
|
||||
|
||||
// 在面a中,找到远离边的方向(与边垂直且指向面内部)
|
||||
// 法向 n_a 叉积 边方向 e_dir → 得到指向面内部垂直于边的方向
|
||||
Vector3D inward_a = cross(n_a, e_dir);
|
||||
double ia_len = std::sqrt(inward_a.x() * inward_a.x() + inward_a.y() * inward_a.y() + inward_a.z() * inward_a.z());
|
||||
if (ia_len < 1e-12) return mesh;
|
||||
inward_a = Vector3D(inward_a.x() / ia_len, inward_a.y() / ia_len, inward_a.z() / ia_len);
|
||||
|
||||
// 确保 inward_a 指向面内部(远离边的方向)
|
||||
// 检查对面b的法向投影:如果 inward_a 指向 b 面则为正向
|
||||
double proj = inward_a.x() * n_b.x() + inward_a.y() * n_b.y() + inward_a.z() * n_b.z();
|
||||
if (proj > 0) {
|
||||
inward_a = Vector3D(-inward_a.x(), -inward_a.y(), -inward_a.z());
|
||||
}
|
||||
|
||||
// 在面b中,同理求 inward_b
|
||||
Vector3D inward_b = cross(n_b, e_dir);
|
||||
double ib_len = std::sqrt(inward_b.x() * inward_b.x() + inward_b.y() * inward_b.y() + inward_b.z() * inward_b.z());
|
||||
if (ib_len < 1e-12) return mesh;
|
||||
inward_b = Vector3D(inward_b.x() / ib_len, inward_b.y() / ib_len, inward_b.z() / ib_len);
|
||||
|
||||
double proj_b = inward_b.x() * n_a.x() + inward_b.y() * n_a.y() + inward_b.z() * n_a.z();
|
||||
if (proj_b > 0) {
|
||||
inward_b = Vector3D(-inward_b.x(), -inward_b.y(), -inward_b.z());
|
||||
}
|
||||
|
||||
// 偏移边的两个端点 → 生成倒角面
|
||||
// p0 offset by dist1 along inward_a → q0a
|
||||
// p0 offset by dist2 along inward_b → q0b
|
||||
// p1 offset by dist1 along inward_a → q1a
|
||||
// p1 offset by dist2 along inward_b → q1b
|
||||
Point3D q0a(p0.x() + dist1 * inward_a.x(), p0.y() + dist1 * inward_a.y(), p0.z() + dist1 * inward_a.z());
|
||||
Point3D q0b(p0.x() + dist2 * inward_b.x(), p0.y() + dist2 * inward_b.y(), p0.z() + dist2 * inward_b.z());
|
||||
Point3D q1a(p1.x() + dist1 * inward_a.x(), p1.y() + dist1 * inward_a.y(), p1.z() + dist1 * inward_a.z());
|
||||
Point3D q1b(p1.x() + dist2 * inward_b.x(), p1.y() + dist2 * inward_b.y(), p1.z() + dist2 * inward_b.z());
|
||||
|
||||
// ── 构建新网格 ──
|
||||
std::vector<Point3D> new_verts;
|
||||
std::vector<std::array<int, 3>> new_tris;
|
||||
std::unordered_set<int> exclude_faces = {face_a, face_b};
|
||||
|
||||
for (size_t i = 0; i < mesh.num_vertices(); ++i)
|
||||
new_verts.push_back(mesh.vertex(i));
|
||||
|
||||
for (size_t fi = 0; fi < mesh.num_faces(); ++fi) {
|
||||
if (exclude_faces.count(static_cast<int>(fi))) continue;
|
||||
auto fv = mesh.face_vertices(static_cast<int>(fi));
|
||||
if (fv.size() == 3)
|
||||
new_tris.push_back({fv[0], fv[1], fv[2]});
|
||||
}
|
||||
|
||||
// ── 3. 在角点插入新面(倒角面) ──
|
||||
// 4个新顶点
|
||||
int i_q0a = static_cast<int>(new_verts.size()); new_verts.push_back(q0a);
|
||||
int i_q0b = static_cast<int>(new_verts.size()); new_verts.push_back(q0b);
|
||||
int i_q1a = static_cast<int>(new_verts.size()); new_verts.push_back(q1a);
|
||||
int i_q1b = static_cast<int>(new_verts.size()); new_verts.push_back(q1b);
|
||||
|
||||
// 倒角面:四边形 q0a-q1a-q1b-q0b → 两个三角形
|
||||
new_tris.push_back({i_q0a, i_q1a, i_q1b});
|
||||
new_tris.push_back({i_q0a, i_q1b, i_q0b});
|
||||
|
||||
// ── 缝合:连接 offset 边到原始网格 ──
|
||||
// 在 face_a 侧:需要将 q0a, q1a 与 face_a 中其他未受影响的顶点连接
|
||||
// 简化方案:用 face_a 中不与边相邻的第三顶点,与 q0a, q1a 形成新面
|
||||
auto fv_a = mesh.face_vertices(face_a);
|
||||
int third_a = -1;
|
||||
for (int vi : fv_a) {
|
||||
if (vi != v0 && vi != v1) { third_a = vi; break; }
|
||||
}
|
||||
if (third_a >= 0) {
|
||||
new_tris.push_back({v0, third_a, i_q0a});
|
||||
new_tris.push_back({v1, i_q1a, third_a});
|
||||
}
|
||||
|
||||
// face_b 侧
|
||||
auto fv_b = mesh.face_vertices(face_b);
|
||||
int third_b = -1;
|
||||
for (int vi : fv_b) {
|
||||
if (vi != v0 && vi != v1) { third_b = vi; break; }
|
||||
}
|
||||
if (third_b >= 0) {
|
||||
new_tris.push_back({v0, i_q0b, third_b});
|
||||
new_tris.push_back({v1, third_b, i_q1b});
|
||||
}
|
||||
|
||||
HalfedgeMesh result;
|
||||
result.build_from_triangles(new_verts, new_tris);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// Mesh Shell — 抽壳
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
|
||||
HalfedgeMesh mesh_shell(const HalfedgeMesh& mesh,
|
||||
double thickness,
|
||||
int face_index) {
|
||||
// ── 1. 复制网格(外壳) ──
|
||||
size_t nv = mesh.num_vertices();
|
||||
size_t nf = mesh.num_faces();
|
||||
|
||||
if (nv == 0 || nf == 0) return mesh;
|
||||
|
||||
// ── 2. 计算每个顶点的平均法向(邻面法向的面积加权平均) ──
|
||||
std::vector<Vector3D> vnormals(nv, Vector3D(0, 0, 0));
|
||||
for (size_t fi = 0; fi < nf; ++fi) {
|
||||
// 跳过开口面
|
||||
if (static_cast<int>(fi) == face_index) continue;
|
||||
|
||||
auto fv = mesh.face_vertices(static_cast<int>(fi));
|
||||
if (fv.size() < 3) continue;
|
||||
|
||||
const auto& p0 = mesh.vertex(static_cast<size_t>(fv[0]));
|
||||
const auto& p1 = mesh.vertex(static_cast<size_t>(fv[1]));
|
||||
const auto& p2 = mesh.vertex(static_cast<size_t>(fv[2]));
|
||||
|
||||
Vector3D e1(p1.x() - p0.x(), p1.y() - p0.y(), p1.z() - p0.z());
|
||||
Vector3D e2(p2.x() - p0.x(), p2.y() - p0.y(), p2.z() - p0.z());
|
||||
|
||||
Vector3D fn = cross(e1, e2);
|
||||
double area = 0.5 * std::sqrt(fn.x() * fn.x() + fn.y() * fn.y() + fn.z() * fn.z());
|
||||
|
||||
for (int vi : fv) {
|
||||
vnormals[static_cast<size_t>(vi)] = Vector3D(
|
||||
vnormals[static_cast<size_t>(vi)].x() + area * fn.x(),
|
||||
vnormals[static_cast<size_t>(vi)].y() + area * fn.y(),
|
||||
vnormals[static_cast<size_t>(vi)].z() + area * fn.z()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 归一化顶点法向
|
||||
for (size_t i = 0; i < nv; ++i) {
|
||||
double len = std::sqrt(vnormals[i].x() * vnormals[i].x() +
|
||||
vnormals[i].y() * vnormals[i].y() +
|
||||
vnormals[i].z() * vnormals[i].z());
|
||||
if (len > 1e-12) {
|
||||
vnormals[i] = Vector3D(vnormals[i].x() / len,
|
||||
vnormals[i].y() / len,
|
||||
vnormals[i].z() / len);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. 沿法向偏移顶点 → 内壳 ──
|
||||
std::vector<Point3D> out_verts, in_verts;
|
||||
for (size_t i = 0; i < nv; ++i) {
|
||||
const auto& p = mesh.vertex(i);
|
||||
out_verts.push_back(p);
|
||||
in_verts.push_back(Point3D(
|
||||
p.x() + thickness * vnormals[i].x(),
|
||||
p.y() + thickness * vnormals[i].y(),
|
||||
p.z() + thickness * vnormals[i].z()
|
||||
));
|
||||
}
|
||||
|
||||
// ── 4. 构建三角面:外壳(正向)+ 内壳(反向 winding) ──
|
||||
std::vector<Point3D> all_verts;
|
||||
std::vector<std::array<int, 3>> all_tris;
|
||||
|
||||
// 外壳顶点 (0..nv-1)
|
||||
for (const auto& v : out_verts) all_verts.push_back(v);
|
||||
// 内壳顶点 (nv..2*nv-1)
|
||||
for (const auto& v : in_verts) all_verts.push_back(v);
|
||||
|
||||
int in_offset = static_cast<int>(nv);
|
||||
|
||||
for (size_t fi = 0; fi < nf; ++fi) {
|
||||
auto fv = mesh.face_vertices(static_cast<int>(fi));
|
||||
if (fv.size() != 3) continue;
|
||||
|
||||
// 外壳面(正向 winding)
|
||||
all_tris.push_back({fv[0], fv[1], fv[2]});
|
||||
|
||||
// 内壳面(反向 winding)
|
||||
if (static_cast<int>(fi) != face_index) {
|
||||
all_tris.push_back({fv[0] + in_offset, fv[2] + in_offset, fv[1] + in_offset});
|
||||
}
|
||||
}
|
||||
|
||||
// ── 5. 缝合边界 → 生成侧壁三角面 ──
|
||||
// 遍历所有边,识别仅被一个面使用的边界边(开口面边缘)
|
||||
// 简化方案:对开口面 face_index 的每条边生成侧壁
|
||||
if (face_index >= 0 && static_cast<size_t>(face_index) < nf) {
|
||||
auto fv = mesh.face_vertices(face_index);
|
||||
size_t fvn = fv.size();
|
||||
for (size_t j = 0; j < fvn; ++j) {
|
||||
int a = fv[j];
|
||||
int b = fv[(j + 1) % fvn];
|
||||
// 侧壁四边形:外 a→b 和内 a→b
|
||||
int a_out = a, b_out = b;
|
||||
int a_in = a + in_offset, b_in = b + in_offset;
|
||||
all_tris.push_back({a_out, b_out, b_in});
|
||||
all_tris.push_back({a_out, b_in, a_in});
|
||||
}
|
||||
}
|
||||
|
||||
HalfedgeMesh result;
|
||||
result.build_from_triangles(all_verts, all_tris);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
// Mesh Draft — 拔模
|
||||
// ═════════════════════════════════════════════════════════════
|
||||
|
||||
HalfedgeMesh mesh_draft(const HalfedgeMesh& mesh,
|
||||
const std::vector<int>& face_indices,
|
||||
double angle,
|
||||
const Vector3D& pull_direction) {
|
||||
if (face_indices.empty() || mesh.num_faces() == 0)
|
||||
return mesh;
|
||||
|
||||
// ── 归一化拔模方向 ──
|
||||
double pd_len = std::sqrt(pull_direction.x() * pull_direction.x() +
|
||||
pull_direction.y() * pull_direction.y() +
|
||||
pull_direction.z() * pull_direction.z());
|
||||
if (pd_len < 1e-12) return mesh;
|
||||
Vector3D dir(pull_direction.x() / pd_len,
|
||||
pull_direction.y() / pd_len,
|
||||
pull_direction.z() / pd_len);
|
||||
|
||||
// ── 收集需要拔模的面和它们的顶点 ──
|
||||
std::unordered_set<int> draft_faces(face_indices.begin(), face_indices.end());
|
||||
std::unordered_set<int> affected_verts;
|
||||
|
||||
for (int fi : face_indices) {
|
||||
if (fi < 0 || static_cast<size_t>(fi) >= mesh.num_faces()) continue;
|
||||
auto fv = mesh.face_vertices(fi);
|
||||
for (int vi : fv)
|
||||
affected_verts.insert(vi);
|
||||
}
|
||||
|
||||
// ── 为每个受影响的面计算旋转轴和旋转矩阵 ──
|
||||
// 拔模原理:将面的顶点绕旋转轴(面法向 × pull_direction)旋转 angle 度
|
||||
// 旋转中心:面的中心点(所有顶点的平均值)
|
||||
|
||||
double cos_a = std::cos(angle);
|
||||
double sin_a = std::sin(angle);
|
||||
|
||||
// 对于每个顶点,计算其在面中的旋转轴
|
||||
// 为每个面单独处理
|
||||
std::unordered_map<int, Vector3D> face_normals_map;
|
||||
std::unordered_map<int, Point3D> face_centers_map;
|
||||
std::unordered_map<int, Vector3D> face_axes_map;
|
||||
|
||||
for (int fi : face_indices) {
|
||||
if (fi < 0 || static_cast<size_t>(fi) >= mesh.num_faces()) continue;
|
||||
auto fv = mesh.face_vertices(fi);
|
||||
if (fv.size() < 3) continue;
|
||||
|
||||
// 面的中心
|
||||
double cx = 0, cy = 0, cz = 0;
|
||||
for (int vi : fv) {
|
||||
const auto& p = mesh.vertex(static_cast<size_t>(vi));
|
||||
cx += p.x(); cy += p.y(); cz += p.z();
|
||||
}
|
||||
cx /= fv.size(); cy /= fv.size(); cz /= fv.size();
|
||||
Point3D center(cx, cy, cz);
|
||||
|
||||
// 面的法向
|
||||
const auto& p0 = mesh.vertex(static_cast<size_t>(fv[0]));
|
||||
const auto& p1 = mesh.vertex(static_cast<size_t>(fv[1]));
|
||||
const auto& p2 = mesh.vertex(static_cast<size_t>(fv[2]));
|
||||
|
||||
Vector3D e1(p1.x() - p0.x(), p1.y() - p0.y(), p1.z() - p0.z());
|
||||
Vector3D e2(p2.x() - p0.x(), p2.y() - p0.y(), p2.z() - p0.z());
|
||||
Vector3D fn = cross(e1, e2);
|
||||
double fn_len = std::sqrt(fn.x() * fn.x() + fn.y() * fn.y() + fn.z() * fn.z());
|
||||
if (fn_len < 1e-12) continue;
|
||||
fn = Vector3D(fn.x() / fn_len, fn.y() / fn_len, fn.z() / fn_len);
|
||||
|
||||
// 旋转轴 = 面法向 × pull_direction
|
||||
Vector3D axis = cross(fn, dir);
|
||||
double ax_len = std::sqrt(axis.x() * axis.x() + axis.y() * axis.y() + axis.z() * axis.z());
|
||||
if (ax_len < 1e-12) {
|
||||
// 面法向与拔模方向平行,无法拔模
|
||||
continue;
|
||||
}
|
||||
axis = Vector3D(axis.x() / ax_len, axis.y() / ax_len, axis.z() / ax_len);
|
||||
|
||||
face_normals_map[fi] = fn;
|
||||
face_centers_map[fi] = center;
|
||||
face_axes_map[fi] = axis;
|
||||
}
|
||||
|
||||
// ── 应用旋转到受影响顶点 ──
|
||||
std::vector<Point3D> new_verts;
|
||||
for (size_t i = 0; i < mesh.num_vertices(); ++i)
|
||||
new_verts.push_back(mesh.vertex(i));
|
||||
|
||||
for (int vi : affected_verts) {
|
||||
Point3D pt = mesh.vertex(static_cast<size_t>(vi));
|
||||
// 使用第一个包含该顶点的拔模面来确定旋转(简化处理)
|
||||
for (int fi : face_indices) {
|
||||
auto fv = mesh.face_vertices(fi);
|
||||
bool found = false;
|
||||
for (int fvi : fv) {
|
||||
if (fvi == vi) { found = true; break; }
|
||||
}
|
||||
if (!found) continue;
|
||||
if (face_axes_map.find(fi) == face_axes_map.end()) continue;
|
||||
|
||||
const Vector3D& axis = face_axes_map[fi];
|
||||
const Point3D& center = face_centers_map[fi];
|
||||
|
||||
// Rodrigues' rotation 绕穿过 center 的轴旋转
|
||||
// v = pt - center
|
||||
Vector3D v(pt.x() - center.x(), pt.y() - center.y(), pt.z() - center.z());
|
||||
|
||||
double dot_kv = axis.x() * v.x() + axis.y() * v.y() + axis.z() * v.z();
|
||||
Vector3D kxv = cross(axis, v);
|
||||
|
||||
Vector3D v_rot(
|
||||
v.x() * cos_a + kxv.x() * sin_a + axis.x() * dot_kv * (1.0 - cos_a),
|
||||
v.y() * cos_a + kxv.y() * sin_a + axis.y() * dot_kv * (1.0 - cos_a),
|
||||
v.z() * cos_a + kxv.z() * sin_a + axis.z() * dot_kv * (1.0 - cos_a)
|
||||
);
|
||||
|
||||
new_verts[static_cast<size_t>(vi)] = Point3D(
|
||||
center.x() + v_rot.x(),
|
||||
center.y() + v_rot.y(),
|
||||
center.z() + v_rot.z()
|
||||
);
|
||||
break; // 只用第一个包含该顶点的面
|
||||
}
|
||||
}
|
||||
|
||||
// ── 重建网格 ──
|
||||
std::vector<std::array<int, 3>> tris;
|
||||
for (size_t fi = 0; fi < mesh.num_faces(); ++fi) {
|
||||
auto fv = mesh.face_vertices(static_cast<int>(fi));
|
||||
if (fv.size() == 3)
|
||||
tris.push_back({fv[0], fv[1], fv[2]});
|
||||
}
|
||||
|
||||
HalfedgeMesh result;
|
||||
result.build_from_triangles(new_verts, tris);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace vde::mesh
|
||||
@@ -10,4 +10,3 @@ add_vde_test(test_fea_mesh)
|
||||
add_vde_test(test_hex_mesh)
|
||||
add_vde_test(test_visualization)
|
||||
add_vde_test(test_mesh_to_curve)
|
||||
add_vde_test(test_mesh_blend)
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/mesh/mesh_blend.h"
|
||||
#include "vde/mesh/halfedge_mesh.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::mesh;
|
||||
|
||||
// ── Helper: 创建立方体网格(12三角面,8顶点) ──
|
||||
static HalfedgeMesh make_cube_mesh(double size = 1.0) {
|
||||
HalfedgeMesh mesh;
|
||||
double h = size / 2.0;
|
||||
mesh.build_from_triangles(
|
||||
{
|
||||
Point3D(-h, -h, -h), Point3D( h, -h, -h), Point3D( h, h, -h), Point3D(-h, h, -h),
|
||||
Point3D(-h, -h, h), Point3D( h, -h, h), Point3D( h, h, h), Point3D(-h, h, h),
|
||||
},
|
||||
{
|
||||
{0, 2, 1}, {0, 3, 2}, // 前 (-z)
|
||||
{4, 5, 6}, {4, 6, 7}, // 后 (+z)
|
||||
{0, 1, 5}, {0, 5, 4}, // 下 (-y)
|
||||
{2, 3, 7}, {2, 7, 6}, // 上 (+y)
|
||||
{0, 4, 7}, {0, 7, 3}, // 左 (-x)
|
||||
{1, 2, 6}, {1, 6, 5}, // 右 (+x)
|
||||
}
|
||||
);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// ── Helper: 创建简单四面体(4顶点, 4三角面) ──
|
||||
static HalfedgeMesh make_tetra_mesh() {
|
||||
HalfedgeMesh mesh;
|
||||
mesh.build_from_triangles(
|
||||
{
|
||||
Point3D(0, 0, 0), Point3D(1, 0, 0),
|
||||
Point3D(0.5, 0.866, 0), Point3D(0.5, 0.289, 0.816),
|
||||
},
|
||||
{
|
||||
{0, 1, 2}, // 底面
|
||||
{0, 3, 1},
|
||||
{1, 3, 2},
|
||||
{2, 3, 0},
|
||||
}
|
||||
);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Mesh Fillet Tests (×3)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(MeshBlendTest, Fillet_ReturnsValidMesh) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
ASSERT_GT(cube.num_edges(), 0);
|
||||
|
||||
// 对边0(前下面的一条边)做圆角
|
||||
HalfedgeMesh result = mesh_fillet(cube, 0, 0.05, 4);
|
||||
|
||||
// 结果必须是有效网格
|
||||
EXPECT_GT(result.num_vertices(), 0);
|
||||
EXPECT_GT(result.num_faces(), 0);
|
||||
// 圆角会新增顶点和面
|
||||
EXPECT_GT(result.num_vertices(), cube.num_vertices());
|
||||
}
|
||||
|
||||
TEST(MeshBlendTest, Fillet_InvalidEdgeReturnsOriginal) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
size_t invalid_idx = cube.num_edges() + 10;
|
||||
|
||||
HalfedgeMesh result = mesh_fillet(cube, invalid_idx, 0.1, 4);
|
||||
|
||||
// 无效边应返回原始网格副本
|
||||
EXPECT_EQ(result.num_vertices(), cube.num_vertices());
|
||||
EXPECT_EQ(result.num_edges(), cube.num_edges());
|
||||
}
|
||||
|
||||
TEST(MeshBlendTest, Fillet_ZeroRadiusReturnsOriginal) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
|
||||
HalfedgeMesh result = mesh_fillet(cube, 0, 0.0, 4);
|
||||
|
||||
// 零半径应返回原始网格
|
||||
EXPECT_EQ(result.num_vertices(), cube.num_vertices());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Mesh Chamfer Tests (×3)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(MeshBlendTest, Chamfer_ReturnsValidMesh) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
ASSERT_GT(cube.num_edges(), 0);
|
||||
|
||||
HalfedgeMesh result = mesh_chamfer(cube, 0, 0.05, 0.05);
|
||||
|
||||
EXPECT_GT(result.num_vertices(), 0);
|
||||
EXPECT_GT(result.num_faces(), 0);
|
||||
// 倒角会新增顶点(4个offset顶点)
|
||||
EXPECT_GT(result.num_vertices(), cube.num_vertices());
|
||||
}
|
||||
|
||||
TEST(MeshBlendTest, Chamfer_InvalidEdgeReturnsOriginal) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
size_t invalid_idx = cube.num_edges() + 5;
|
||||
|
||||
HalfedgeMesh result = mesh_chamfer(cube, invalid_idx, 0.05);
|
||||
|
||||
EXPECT_EQ(result.num_vertices(), cube.num_vertices());
|
||||
EXPECT_EQ(result.num_edges(), cube.num_edges());
|
||||
}
|
||||
|
||||
TEST(MeshBlendTest, Chamfer_Dist2Default) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
|
||||
// dist2 < 0 应使用 dist1
|
||||
HalfedgeMesh result = mesh_chamfer(cube, 0, 0.05, -1.0);
|
||||
|
||||
EXPECT_GT(result.num_vertices(), cube.num_vertices());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Mesh Shell Tests (×3)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(MeshBlendTest, Shell_ClosedShell) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
|
||||
// 完全封闭抽壳(face_index = -1)
|
||||
HalfedgeMesh result = mesh_shell(cube, 0.1, -1);
|
||||
|
||||
EXPECT_GT(result.num_vertices(), 0);
|
||||
EXPECT_GT(result.num_faces(), 0);
|
||||
// 抽壳:外壳 + 内壳 = 2×顶点
|
||||
EXPECT_EQ(result.num_vertices(), cube.num_vertices() * 2);
|
||||
// 面数:外壳12 + 内壳12 = 24
|
||||
EXPECT_EQ(result.num_faces(), cube.num_faces() * 2);
|
||||
}
|
||||
|
||||
TEST(MeshBlendTest, Shell_WithOpenFace) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
|
||||
// 顶部开口(face_index=0,即前面)
|
||||
HalfedgeMesh result = mesh_shell(cube, 0.1, 0);
|
||||
|
||||
EXPECT_GT(result.num_vertices(), 0);
|
||||
EXPECT_GT(result.num_faces(), 0);
|
||||
// 顶点 = 2×原始(外壳+内壳)
|
||||
EXPECT_EQ(result.num_vertices(), cube.num_vertices() * 2);
|
||||
}
|
||||
|
||||
TEST(MeshBlendTest, Shell_ZeroThickness) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
|
||||
HalfedgeMesh result = mesh_shell(cube, 0.0, -1);
|
||||
|
||||
// 零厚度抽壳仍然生成内外壳
|
||||
EXPECT_EQ(result.num_vertices(), cube.num_vertices() * 2);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Mesh Draft Tests (×3)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(MeshBlendTest, Draft_ReturnsValidMesh) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
|
||||
// 对前面(face 0)拔模 5°
|
||||
HalfedgeMesh result = mesh_draft(cube, {0}, M_PI / 36.0,
|
||||
Vector3D(0, 0, 1));
|
||||
|
||||
EXPECT_EQ(result.num_vertices(), cube.num_vertices());
|
||||
EXPECT_EQ(result.num_faces(), cube.num_faces());
|
||||
}
|
||||
|
||||
TEST(MeshBlendTest, Draft_EmptyFaceListReturnsOriginal) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
|
||||
HalfedgeMesh result = mesh_draft(cube, {}, M_PI / 18.0,
|
||||
Vector3D(0, 0, 1));
|
||||
|
||||
EXPECT_EQ(result.num_vertices(), cube.num_vertices());
|
||||
EXPECT_EQ(result.num_faces(), cube.num_faces());
|
||||
}
|
||||
|
||||
TEST(MeshBlendTest, Draft_MultipleFaces) {
|
||||
HalfedgeMesh cube = make_cube_mesh(2.0);
|
||||
|
||||
// 对侧面拔模(面4、5是底面,面6、7是顶面,它们的法向不平行于Z轴)
|
||||
HalfedgeMesh result = mesh_draft(cube, {4, 5, 6, 7}, M_PI / 18.0,
|
||||
Vector3D(0, 0, 1));
|
||||
|
||||
EXPECT_EQ(result.num_vertices(), cube.num_vertices());
|
||||
EXPECT_EQ(result.num_faces(), cube.num_faces());
|
||||
|
||||
// 拔模后顶点位置应有变化
|
||||
bool any_changed = false;
|
||||
for (size_t i = 0; i < result.num_vertices() && i < cube.num_vertices(); ++i) {
|
||||
double dx = result.vertex(i).x() - cube.vertex(i).x();
|
||||
double dy = result.vertex(i).y() - cube.vertex(i).y();
|
||||
double dz = result.vertex(i).z() - cube.vertex(i).z();
|
||||
if (std::abs(dx) > 1e-9 || std::abs(dy) > 1e-9 || std::abs(dz) > 1e-9) {
|
||||
any_changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(any_changed);
|
||||
}
|
||||
Reference in New Issue
Block a user