// 标准C++库头文件
#include <iostream> // 输入输出流
#include <fstream> // 文件流操作
#include <limits> // 数值限制
#include <string> // 字符串处理
#include <filesystem> // 文件系统操作
#include <unordered_map> // 无序映射容器
#include <list> // 链表容器
#include <utility> // 工具函数
#include <vector> // 向量容器
// Assimp库头文件 - 用于3D模型导入导出
#include <assimp/Importer.hpp> // 模型导入器
#include <assimp/Exporter.hpp> // 模型导出器
#include <assimp/mesh.h> // 网格数据结构
#include <assimp/postprocess.h> // 后处理选项
#include <assimp/scene.h> // 场景数据结构
// CGAL库头文件 - 用于计算几何和网格处理
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h> // 精确谓词内核
#include <CGAL/Polygon_mesh_processing/bbox.h> // 包围盒计算
#include <CGAL/Polygon_mesh_processing/connected_components.h> // 连通分量
#include <CGAL/Polygon_mesh_processing/clip.h> // 网格裁剪
#include <CGAL/Polygon_mesh_processing/corefinement.h> // 核心细化
#include <CGAL/Polygon_mesh_processing/polygon_mesh_to_polygon_soup.h> // 网格转多边形汤
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h> // 多边形汤转网格
#include <CGAL/Polygon_mesh_processing/repair_polygon_soup.h> // 修复多边形汤
#include <CGAL/Polygon_mesh_processing/orientation.h> // 方向处理
#include <CGAL/Polygon_mesh_processing/remesh.h> // 重新网格化
#include <CGAL/Surface_mesh.h> // 表面网格
#include <CGAL/Surface_mesh_parameterization/parameterize.h> // 参数化
#include <CGAL/Surface_mesh_parameterization/ARAP_parameterizer_3.h> // ARAP参数化器
#include <CGAL/Surface_mesh_parameterization/Mean_value_coordinates_parameterizer_3.h> // 均值坐标参数化器
#include <CGAL/Surface_mesh_parameterization/LSCM_parameterizer_3.h> // LSCM参数化器
#include <CGAL/Surface_mesh_parameterization/Discrete_authalic_parameterizer_3.h> // 离散等面积参数化器
#include <CGAL/Surface_mesh_parameterization/Barycentric_mapping_parameterizer_3.h> // 重心映射参数化器
#include <CGAL/Surface_mesh_parameterization/Discrete_conformal_map_parameterizer_3.h> // 离散共形映射参数化器
#include <CGAL/Surface_mesh_parameterization/Iterative_authalic_parameterizer_3.h> // 迭代等面积参数化器
#include <CGAL/Optimal_bounding_box/oriented_bounding_box.h> // 最优包围盒
// 使用标准命名空间
using namespace std;
// 文件系统命名空间别名
namespace fs = std::filesystem;
// CGAL库命名空间别名,简化代码
namespace PMP = CGAL::Polygon_mesh_processing; // 多边形网格处理
namespace SMP = CGAL::Surface_mesh_parameterization; // 表面网格参数化
// 几何内核类型定义 - 使用精确谓词内核,保证数值稳定性
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
//typedef CGAL::Simple_cartesian<double> Kernel; // 备选简单笛卡尔内核
typedef Kernel::Point_2 Point_2; // 2D点类型
typedef Kernel::Point_3 Point; // 3D点类型
typedef CGAL::Surface_mesh<Point> CGALMesh; // CGAL表面网格类型
typedef CGAL::SM_Vertex_index VertexIndex; // 顶点索引类型
//typedef CGALMesh::Property_map<VertexIndex, Kernel::Point_2> VertexUVPmap; // UV坐标属性映射
// 其他几何类型定义
typedef Kernel::Vector_3 Vector_3; // 3D向量
typedef Kernel::Plane_3 Plane_3; // 3D平面
typedef Kernel::Aff_transformation_3 Aff_transformation_3; // 仿射变换
namespace OB = CGAL::Optimal_bounding_box; // 最优包围盒命名空间
// 网格实体类型 - 包含网格、颜色和名称的元组
using MeshEntity = std::tuple<CGALMesh, aiColor3D, std::string>;
/**
* 剖切面访问者类 - 用于在网格裁剪过程中收集剖切面
* 这个类实现了CGAL的访问者模式,用于跟踪裁剪操作并生成剖切面
*/
struct SectionVisitor {
CGALMesh& section; // 存储生成的剖切面网格
std::map<CGALMesh::Vertex_index, CGALMesh::Vertex_index> vmap; // 顶点映射表,记录原网格顶点到剖切面顶点的映射
SectionVisitor(CGALMesh& sm) : section(sm) {} // 构造函数,传入剖切面网格引用
// 图论相关的类型定义,用于网格处理
typedef CGALMesh TriangleMesh; // 三角形网格类型
typedef boost::graph_traits<TriangleMesh> GT; // 图特征类型
typedef typename GT::face_descriptor face_descriptor; // 面描述符
typedef typename GT::halfedge_descriptor halfedge_descriptor; // 半边描述符
typedef typename GT::vertex_descriptor vertex_descriptor; // 顶点描述符
// face visitor functions
void before_subface_creations(face_descriptor /*f_old*/, TriangleMesh&) {}
void after_subface_creations(TriangleMesh&) {}
void before_subface_created(TriangleMesh&) {}
void after_subface_created(face_descriptor, TriangleMesh&) {}
void before_face_copy(face_descriptor, const TriangleMesh&, TriangleMesh&) {}
void after_face_copy(face_descriptor f_old, const TriangleMesh& tm_old,
face_descriptor /*f_new*/, TriangleMesh& /*tm_new*/)
{
std::vector<CGALMesh::Vertex_index> vertices;
for (auto v : vertices_around_face(tm_old.halfedge(f_old), tm_old)) {
auto it = vmap.find(v);
if (it != vmap.end()) {
vertices.push_back(it->second);
}
}
if (vertices.size() == 3) {
section.add_face(vertices[0], vertices[1], vertices[2]);
}
}
// edge visitor functions
void before_edge_split(halfedge_descriptor /* h */, TriangleMesh& /* tm */) {}
void edge_split(halfedge_descriptor /* hnew */, TriangleMesh& /* tm */) {}
void after_edge_split() {}
void add_retriangulation_edge(halfedge_descriptor /* h */, TriangleMesh& /* tm */) {} // edges added during split face retriangulation
void before_edge_copy(halfedge_descriptor /*h_old*/, const TriangleMesh&, TriangleMesh&) {}
void after_edge_copy(halfedge_descriptor /*h_old*/, const TriangleMesh&,
halfedge_descriptor /* f_new */, TriangleMesh&) {}
void before_edge_duplicated(halfedge_descriptor /*h_old*/, TriangleMesh&) {} // called before a patch border edge is duplicated
void after_edge_duplicated(halfedge_descriptor /*h_old*/,
halfedge_descriptor /* f_new */, TriangleMesh&) {} // called after a patch border edge is duplicated
void intersection_edge_copy(halfedge_descriptor /* h_old1 */, const TriangleMesh& /* tm1 */,
halfedge_descriptor /* h_old2 */, const TriangleMesh& /* tm2 */,
halfedge_descriptor /* h_new */, TriangleMesh& /* tm_new */) {}
// vertex visitor functions
void new_vertex_added(std::size_t /*node_id*/,
vertex_descriptor vh,
const TriangleMesh& tm)
{
vmap[vh] = section.add_vertex(tm.point(vh));
}
void intersection_point_detected(std::size_t /* node_id */,
int /* sdim */,
halfedge_descriptor /* principal_edge */,
halfedge_descriptor /* additional_edge */,
const TriangleMesh& /* tm1 */,
const TriangleMesh& /* tm2 */,
bool /* is_target_coplanar */,
bool /* is_source_coplanar */) {}
void before_vertex_copy(vertex_descriptor /*v_src*/, const TriangleMesh& /*tm_src*/, TriangleMesh& /*tm_tgt*/) {}
void after_vertex_copy(vertex_descriptor v_src, const TriangleMesh& tm_src,
vertex_descriptor /*v_tgt*/, TriangleMesh& /*tm_tgt*/)
{
vmap[v_src] = section.add_vertex(tm_src.point(v_src));
}
// progress tracking
void start_filtering_intersections() const {}
void progress_filtering_intersections(double) const {}
void end_filtering_intersections() const {}
void start_triangulating_faces(std::size_t) const {}
void triangulating_faces_step() const {}
void end_triangulating_faces() const {}
void start_handling_intersection_of_coplanar_faces(std::size_t) const {}
void intersection_of_coplanar_faces_step() const {}
void end_handling_intersection_of_coplanar_faces() const {}
void start_handling_edge_face_intersections(std::size_t) const {}
void edge_face_intersections_step() const {}
void end_handling_edge_face_intersections() const {}
void start_building_output() const {}
void end_building_output() const {}
// Required by Face_graph_output_builder
void filter_coplanar_edges() const {}
void detect_patches() const {}
void classify_patches() const {}
void classify_intersection_free_patches(const TriangleMesh&) const {}
void out_of_place_operation(PMP::Corefinement::Boolean_operation_type) const {}
void in_place_operation(PMP::Corefinement::Boolean_operation_type) const {}
void in_place_operations(PMP::Corefinement::Boolean_operation_type,
PMP::Corefinement::Boolean_operation_type) const {}
};
/**
* 读取剖切曲线文件
* @param pth 曲线文件路径
* @return 包含3D点坐标的向量
*
* 功能:从文本文件中读取3D点坐标,每行包含x y z三个浮点数
* 文件格式:每行一个3D点,坐标用空格分隔
*/
vector<Point> readCurve(const fs::path& pth) {
std::vector<Point> curve; // 存储读取的曲线点
std::ifstream file(pth); // 打开文件流
// 检查文件是否成功打开
if (!file.is_open()) {
std::cerr << "Failed to open file: " << pth << std::endl;
return curve;
}
double x, y, z; // 临时存储坐标值
int pointCount = 0; // 记录读取的点数
// 逐行读取文件中的坐标
while (file >> x >> y >> z) {
// 检查坐标值是否为NaN(非数字)
if (std::isnan(x) || std::isnan(y) || std::isnan(z)) {
std::cerr << "Invalid curve point: NaN detected" << std::endl;
continue; // 跳过无效点
}
curve.push_back(Point(x, y, z)); // 将有效点添加到曲线中
pointCount++;
}
file.close(); // 关闭文件
std::cout << "Read " << pointCount << " points from curve file" << std::endl;
// 检查是否读取到有效点
if (curve.empty()) {
std::cerr << "Warning: No valid points read from curve file!" << std::endl;
}
return curve;
}
/**
* 生成剖切面方法1 - 创建垂直延伸的平面
* @param curve 输入曲线点集
* @return 生成的剖切面网格
*
* 算法:将曲线上的每个点沿Z轴正负方向延伸,形成垂直的平面
* 每个曲线段生成两个三角形,形成连续的垂直平面
*/
CGALMesh generateSurface1(const vector<Point>& curve) {
CGALMesh surface; // 创建空网格
double extensionDistance = 5000; // 延伸距离,沿Z轴正负方向各延伸5000单位
// 检查输入曲线是否为空
if (curve.empty()) {
std::cerr << "Error: Cannot generate surface from empty curve!" << std::endl;
return surface;
}
std::cout << "Generating surface from " << curve.size() << " curve points" << std::endl;
// 为每个曲线点创建两个顶点(上下延伸)
for (const auto& p : curve) {
Point p1(p.x(), p.y(), p.z() - extensionDistance); // 下方顶点
Point p2(p.x(), p.y(), p.z() + extensionDistance); // 上方顶点
surface.add_vertex(p1);
surface.add_vertex(p2);
}
// 连接相邻点形成三角形面片
for (size_t i = 0; i < curve.size() - 1; ++i) {
size_t baseIndex = i * 2; // 当前段的顶点起始索引
// 第一个三角形:当前下顶点 -> 下一上顶点 -> 当前上顶点
surface.add_face(
CGALMesh::Vertex_index(baseIndex),
CGALMesh::Vertex_index(baseIndex + 1),
CGALMesh::Vertex_index(baseIndex + 2)
);
// 第二个三角形:当前上顶点 -> 下一上顶点 -> 下一上顶点
surface.add_face(
CGALMesh::Vertex_index(baseIndex + 1),
CGALMesh::Vertex_index(baseIndex + 3),
CGALMesh::Vertex_index(baseIndex + 2)
);
}
std::cout << "Generated surface with " << surface.number_of_vertices()
<< " vertices and " << surface.number_of_faces() << " faces" << std::endl;
return surface;
}
/**
* 生成剖切面方法2 - 创建更复杂的曲面结构
* @param curve 输入曲线点集
* @return 生成的剖切面网格
*
* 算法:为每个曲线点创建三个顶点(下、中、上),形成更丰富的曲面结构
* 每个曲线段生成四个三角形,创建更平滑的曲面过渡
*/
CGALMesh generateSurface2(const std::vector<Point>& curve) {
CGALMesh surface; // 创建空网格
double extensionDistance = 4000; // 延伸距离
// 为每个曲线点创建三个顶点(下、中、上)
for (const auto& p : curve) {
Point p_down(p.x(), p.y(), p.z()); // 下方顶点(原始高度)
Point p_original(p.x(), p.y(), p.z() + 2000); // 中间顶点(偏移2000)
Point p_up(p.x(), p.y(), p.z() + extensionDistance); // 上方顶点(最大高度)
surface.add_vertex(p_down);
surface.add_vertex(p_original);
surface.add_vertex(p_up);
}
// 连接相邻点形成四个三角形面片(确保逆时针顺序)
for (size_t i = 0; i < curve.size() - 1; ++i) {
// 当前点和下一个点的顶点索引
size_t curr_base = i * 3; // 当前段的顶点起始索引
size_t next_base = (i + 1) * 3; // 下一段的顶点起始索引
// 连接两个相邻截面形成四个三角形
// 三角形1:当前下 → 下一原 → 当前原
surface.add_face(
CGALMesh::Vertex_index(curr_base),
CGALMesh::Vertex_index(next_base + 1),
CGALMesh::Vertex_index(curr_base + 1)
);
// 三角形2:当前原 → 下一原 → 下一上
surface.add_face(
CGALMesh::Vertex_index(curr_base + 1),
CGALMesh::Vertex_index(next_base + 1),
CGALMesh::Vertex_index(next_base + 2)
);
// 三角形3:当前下 → 下一原 → 下一上
surface.add_face(
CGALMesh::Vertex_index(curr_base),
CGALMesh::Vertex_index(next_base + 1),
CGALMesh::Vertex_index(next_base + 2)
);
// 三角形4:当前下 → 下一上 → 当前上
surface.add_face(
CGALMesh::Vertex_index(curr_base),
CGALMesh::Vertex_index(next_base + 2),
CGALMesh::Vertex_index(curr_base + 2)
);
}
/* 注释掉的网格修复代码
std::vector<Point> points;
std::vector<std::vector<size_t>> polygons;
PMP::polygon_mesh_to_polygon_soup(surface, points, polygons);
PMP::repair_polygon_soup(points, polygons);
PMP::orient_polygon_soup(points, polygons);
if (PMP::is_polygon_soup_a_polygon_mesh(polygons)) {
PMP::polygon_soup_to_polygon_mesh(points, polygons, surface);
}*/
return surface;
}
/**
* 计算网格的包围盒
* @param mesh 输入网格
* @return 网格的3D包围盒
*
* 功能:遍历网格的所有顶点,计算整个网格的边界框
*/
CGAL::Bbox_3 compute_mesh_bbox(const CGALMesh& mesh) {
CGAL::Bbox_3 bbox; // 创建空包围盒
for (auto v : mesh.vertices()) { // 遍历所有顶点
const auto& p = mesh.point(v); // 获取顶点坐标
bbox += p.bbox(); // 将顶点添加到包围盒中
}
return bbox;
}
/**
* 计算边界环的包围盒
* @param mesh 输入网格
* @param border_halfedge 边界环的起始半边
* @return 边界环的3D包围盒
*
* 功能:沿着边界环遍历所有顶点,计算边界环的边界框
*/
CGAL::Bbox_3 compute_border_cycle_bbox(const CGALMesh& mesh, CGALMesh::Halfedge_index border_halfedge) {
CGAL::Bbox_3 border_bbox; // 创建边界环包围盒
CGALMesh::Halfedge_index h = border_halfedge; // 从起始半边开始
// 沿着边界环遍历所有半边
do {
auto v = mesh.target(h); // 获取当前半边目标顶点
const auto& p = mesh.point(v); // 获取顶点坐标
border_bbox += p.bbox(); // 将顶点添加到边界环包围盒中
h = mesh.next(h); // 移动到下一个半边
} while (h != border_halfedge); // 直到回到起始半边
return border_bbox;
}
/**
* 判断网格是否为外边界封闭网格
* @param mesh 输入网格
* @return 如果是外边界封闭网格返回true,否则返回false
*
* 功能:判断网格是否只有一个大的外边界环,用于识别需要跳过的网格
* 判断标准:
* 1. 网格不能是完全封闭的
* 2. 只能有一个边界环
* 3. 边界环必须足够大(至少20条边)
* 4. 边界环的包围盒必须覆盖网格包围盒的90%以上
*/
bool is_outer_closed_boundary(const CGALMesh& mesh) {
// 如果网格完全封闭,则不是外边界
if (CGAL::is_closed(mesh)) return false;
// 获取所有边界环
std::vector<CGALMesh::Halfedge_index> border_cycles;
CGAL::Polygon_mesh_processing::extract_boundary_cycles(mesh, std::back_inserter(border_cycles));
// 仅允许单一边界环(排除有多个洞的网格)
if (border_cycles.size() != 1) return false;
// 检查边界环长度(排除小孔洞)
CGALMesh::Halfedge_index border_halfedge = border_cycles.front();
std::size_t edge_count = 0;
CGALMesh::Halfedge_index start = border_halfedge;
do {
edge_count++;
border_halfedge = mesh.next(border_halfedge);
} while (border_halfedge != start);
// 边界环必须足够大(至少20条边)
const std::size_t MIN_BORDER_EDGES = 20;
if (edge_count < MIN_BORDER_EDGES) return false;
// 包围盒验证 - 检查边界环是否覆盖了网格的大部分区域
const CGAL::Bbox_3 mesh_bbox = compute_mesh_bbox(mesh); // 计算整个网格的包围盒
const CGAL::Bbox_3 border_bbox = compute_border_cycle_bbox(mesh, start); // 计算边界环的包围盒
// 计算包围盒在各轴上的覆盖比例
const double x_ratio = (border_bbox.xmax() - border_bbox.xmin()) / (mesh_bbox.xmax() - mesh_bbox.xmin());
const double y_ratio = (border_bbox.ymax() - border_bbox.ymin()) / (mesh_bbox.ymax() - mesh_bbox.ymin());
const double z_ratio = (border_bbox.zmax() - border_bbox.zmin()) / (mesh_bbox.zmax() - mesh_bbox.zmin());
// 设定阈值:边界环包围盒需覆盖整体包围盒的至少90%
const double BBOX_COVERAGE_THRESHOLD = 0.9;
if (x_ratio < BBOX_COVERAGE_THRESHOLD ||
y_ratio < BBOX_COVERAGE_THRESHOLD ||
z_ratio < BBOX_COVERAGE_THRESHOLD) {
return false;
}
return true; // 满足所有条件,是外边界封闭网格
}
bool AssimpFormatId(const Assimp::Exporter& exporter,
const fs::path& filepath, std::string& formatId, bool binary)
{
std::string fpstr(filepath.string());
size_t ipos = fpstr.find_last_of('.');
formatId = fpstr.substr(ipos + 1);
bool findFormat = false;
if (formatId == "gltf" || formatId == "glb") {
findFormat = true;
formatId += "2";
}
else {
for (size_t i = 0; i < exporter.GetExportFormatCount(); i++) {
const aiExportFormatDesc* desc =
exporter.GetExportFormatDescription(i);
if (strstr(desc->id, formatId.c_str()) != NULL) {
findFormat = true;
if (binary) {
if (strstr(desc->id, "b") != NULL) {
formatId = desc->id;
break;
}
}
else break;
}
}
}
return findFormat;
}
/**
* 将Assimp网格转换为CGAL网格
* @param imh 输入的Assimp网格
* @param omh 输出的CGAL网格
* @param name 网格名称
* @return 转换成功返回true,失败返回false
*
* 功能:将Assimp库的网格数据结构转换为CGAL库的网格数据结构
* 包括顶点坐标和面片索引的转换,并进行网格修复和方向调整
*/
static bool AiMeshToCgalMesh(const aiMesh& imh, CGALMesh& omh, std::string& name)
{
// 检查网格是否有效(至少3个顶点和1个面)
if (imh.mNumVertices < 3 || imh.mNumFaces < 1) return false;
// 获取网格名称
if (imh.mName.length > 0) { name = imh.mName.data; }
// 准备顶点和面片数据
std::vector<Point> points; // 存储顶点坐标
std::vector<std::vector<size_t>> polygons; // 存储面片索引
// 转换顶点坐标
points.reserve(imh.mNumVertices);
for (uint32_t i = 0; i < imh.mNumVertices; i++) {
const aiVector3D& vet = imh.mVertices[i]; // 获取Assimp顶点
points.push_back(Point((double)vet.x, (double)vet.y, (double)vet.z)); // 转换为CGAL点
}
// 转换面片索引
polygons.reserve(imh.mNumFaces);
for (uint32_t i = 0; i < imh.mNumFaces; i++)
{
aiFace face = imh.mFaces[i]; // 获取Assimp面片
uint32_t* indices = face.mIndices; // 获取面片顶点索引
polygons.push_back({ (size_t)indices[0], (size_t)indices[1], (size_t)indices[2] }); // 转换为CGAL面片
}
// 修复多边形汤(处理重复顶点、退化面片等)
PMP::repair_polygon_soup(points, polygons);
// 调整多边形汤的方向(确保面片法向量一致)
PMP::orient_polygon_soup(points, polygons);
// 检查是否可以转换为多边形网格
if (PMP::is_polygon_soup_a_polygon_mesh(polygons)) {
PMP::polygon_soup_to_polygon_mesh(points, polygons, omh); // 转换为CGAL网格
return true;
}
return false; // 转换失败
}
static bool CgalMeshToAiMesh(const CGALMesh& imh, const std::string& name, aiMesh& omh)
{
if (imh.is_empty() || imh.number_of_faces() == 0) return false;
omh.mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
omh.mNumVertices = imh.number_of_vertices();
omh.mVertices = new aiVector3D[omh.mNumVertices];
std::unordered_map<VertexIndex, uint32_t> vimap; //! avoid order problem
auto uvmap = imh.property_map<VertexIndex, Kernel::Point_2>("v:uv");
uint32_t vidx = 0;
if (uvmap.second) {
omh.mTextureCoords[0] = new aiVector3D[omh.mNumVertices];
omh.mNumUVComponents[0] = 2;
uint32_t vidx = 0;
for (const auto vd : CGAL::vertices(imh)) {
const auto& point = imh.point(vd);
omh.mVertices[vidx] = aiVector3D(static_cast<ai_real>(point.x()), static_cast<ai_real>(point.y()), static_cast<ai_real>(point.z()));
const auto& uv = boost::get(uvmap.first,vd);
omh.mTextureCoords[0][vidx] = aiVector3D(static_cast<ai_real>(uv.x()), static_cast<ai_real>(uv.y()), 0.0);
vimap[vd] = vidx++;
}
}
else {
omh.mTextureCoords[0] = nullptr;
omh.mNumUVComponents[0] = 0;
uint32_t vidx = 0;
for (const auto vd : CGAL::vertices(imh)) {
const auto& point = imh.point(vd);
omh.mVertices[vidx] = aiVector3D(static_cast<ai_real>(point.x()), static_cast<ai_real>(point.y()), static_cast<ai_real>(point.z()));
vimap[vd] = vidx++;
}
}
omh.mNumFaces = imh.number_of_faces();
omh.mFaces = new aiFace[omh.mNumFaces];
uint32_t fidx = 0;
for (const auto& itr : imh.faces())
{
CGAL::SM_Halfedge_index heidx = imh.halfedge(itr);
const auto& fvids = imh.vertices_around_face(heidx);
auto trif = &omh.mFaces[fidx];
trif->mIndices = new uint32_t[3]{
vimap[*fvids.begin()], vimap[*(++fvids.begin())], vimap[*(--fvids.end())]
};
trif->mNumIndices = 3;
fidx++;
}
omh.mName = name;
return true;
}
static bool ImportMeshes(const fs::path& pth, std::vector<MeshEntity>& mshes)
{
Assimp::Importer ipt;
uint32_t flags = aiProcess_Triangulate | aiProcess_JoinIdenticalVertices;
// read file via ASSIMP
const aiScene* scene = ipt.ReadFile(pth.u8string(), flags);
// check for errors
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero
{
std::cerr << "ERROR::ASSIMP:: " << ipt.GetErrorString() << std::endl;
return false;
}
aiColor3D clr;
std::vector<aiColor3D> cvec;
for (uint32_t i = 0; i < scene->mNumMaterials; i++) {
auto mat = scene->mMaterials[i];
if (mat->Get(AI_MATKEY_BASE_COLOR, clr) != aiReturn_SUCCESS) {
mat->Get(AI_MATKEY_COLOR_DIFFUSE, clr);
}
cvec.push_back(clr);
}
for (uint32_t i = 0; i < scene->mNumMeshes; i++) {
auto imsh = scene->mMeshes[i];
CGALMesh omsh;
std::string name;
if (AiMeshToCgalMesh(*imsh, omsh, name)) {
const auto clr = cvec[imsh->mMaterialIndex];
mshes.push_back({ std::move(omsh), clr, name });
}
}
return true;
}
static bool ExportMeshes(const fs::path& pth, const std::vector<MeshEntity>& items, bool texture)
{
if (items.empty()) {
std::cerr << "Warning: No meshes to export!" << std::endl;
return false;
}
std::cout << "Exporting " << items.size() << " meshes to: " << pth << std::endl;
int midx = 0;
std::vector<aiMesh*> meshes;
std::vector<aiMaterial*> materials;
for (const auto& item : items) {
const auto& mesh = std::get<CGALMesh>(item);
const auto& name = std::get<std::string>(item);
const auto& color = std::get<aiColor3D>(item);
std::cout << "Processing mesh '" << name << "' with " << mesh.number_of_vertices()
<< " vertices and " << mesh.number_of_faces() << " faces" << std::endl;
if (mesh.is_empty()) {
std::cerr << "Warning: Mesh '" << name << "' is empty, skipping" << std::endl;
continue;
}
aiMesh* omsh = new aiMesh;
if (CgalMeshToAiMesh(mesh, name, *omsh)) {
aiMaterial* mat = new aiMaterial;
if (texture) {
mat->AddProperty(new aiString(name + ".png"), AI_MATKEY_TEXTURE_DIFFUSE(0));
}
else {
mat->AddProperty(new aiColor3D(color), 1, AI_MATKEY_COLOR_DIFFUSE);
}
mat->AddProperty(new aiString(name), AI_MATKEY_NAME);
omsh->mMaterialIndex = midx;
meshes.push_back(omsh);
materials.push_back(mat);
midx++;
}
else {
std::cerr << "Warning: Failed to convert mesh '" << name << "' to Assimp format" << std::endl;
delete omsh;
}
}
aiScene* scene = new aiScene();
scene->mNumMeshes = (uint32_t)meshes.size();
scene->mMeshes = new aiMesh * [scene->mNumMeshes];
scene->mNumMaterials = (uint32_t)materials.size();
scene->mMaterials = new aiMaterial * [scene->mNumMaterials];
#ifdef _WIN32
memcpy_s(scene->mMeshes, scene->mNumMeshes * sizeof(aiMesh*),
meshes.data(), scene->mNumMeshes * sizeof(aiMesh*));
memcpy_s(scene->mMaterials, scene->mNumMaterials * sizeof(aiMaterial*),
materials.data(), scene->mNumMaterials * sizeof(aiMaterial*));
#elif defined __GNUC__
memcpy(scene->mMeshes, meshes.data(), scene->mNumMeshes * sizeof(aiMesh*));
memcpy(scene->mMaterials, materials.data(), scene->mNumMaterials * sizeof(aiMaterial*));
#endif
aiNode* node = new aiNode;
node->mNumMeshes = (uint32_t)meshes.size();
node->mMeshes = new uint32_t[node->mNumMeshes];
for (uint32_t i = 0; i < node->mNumMeshes; i++) {
node->mMeshes[i] = i;
}
scene->mRootNode = node;
std::string formatId;
Assimp::Exporter exporter;
int flags = aiProcess_GenSmoothNormals;
if (!AssimpFormatId(exporter, pth, formatId, false)) return false;
return exporter.Export(scene, formatId, pth.u8string(), flags) == aiReturn_SUCCESS;
}
static bool fnRepair(CGALMesh& mesh) {
if (mesh.is_empty()) return false;
CGAL::Polygon_mesh_processing::stitch_borders(mesh);
std::vector<CGALMesh::Halfedge_index> borders;
PMP::extract_boundary_cycles(mesh, std::back_inserter(borders));
if (!borders.empty()) {
std::vector<CGALMesh::Face_index> new_faces;
CGAL::Polygon_mesh_processing::triangulate_hole(
mesh, borders[0], CGAL::parameters::face_output_iterator(std::back_inserter(new_faces)));
}
return true;
}
static bool SectionClipping(CGALMesh& msh, CGALMesh& clp, CGALMesh& section) {
if (msh.is_empty()) {
std::cerr << "Error: Input mesh is empty" << std::endl;
return false;
}
if (clp.is_empty()) {
std::cerr << "Error: Clip surface is empty" << std::endl;
return false;
}
std::cout << "Input mesh has " << msh.number_of_vertices() << " vertices and "
<< msh.number_of_faces() << " faces" << std::endl;
std::cout << "Clip surface has " << clp.number_of_vertices() << " vertices and "
<< clp.number_of_faces() << " faces" << std::endl;
//auto fnRepair = [](CGALMesh& mesh) {
// CGAL::Polygon_mesh_processing::stitch_borders(mesh);
// std::vector<CGALMesh::Halfedge_index> borders;
// //PMP::extract_boundary_cycles(mesh, std::back_inserter(borders));
// for (auto h : mesh.halfedges()) {
// if (mesh.is_border(h)) borders.push_back(h);
// }
// if (!borders.empty()) {
// std::vector<CGALMesh::Face_index> new_faces;
// CGAL::Polygon_mesh_processing::triangulate_hole(
// mesh, borders[0], CGAL::parameters::face_output_iterator(std::back_inserter(new_faces)));
// }
// };
std::vector<CGALMesh> meshes;
PMP::split_connected_components(msh, meshes);
std::cout << "Split into " << meshes.size() << " connected components" << std::endl;
bool status = true;
int processedComponents = 0;
for (auto& spt : meshes) {
size_t nf = spt.number_of_faces();
if (nf < 2) {
std::cout << "Skipping component with only " << nf << " faces" << std::endl;
continue;
}
bool is_out = is_outer_closed_boundary(spt);
if (is_out) {
std::cout << "Skipping outer boundary component" << std::endl;
continue;
}
SectionVisitor visitor(section);
if (!CGAL::is_closed(spt)) {
std::cout << "Repairing non-closed component" << std::endl;
fnRepair(spt);
}
if (!CGAL::is_closed(spt)) {
std::cout << "Component still not closed after repair, skipping" << std::endl;
continue;
}
std::cout << "Clipping component " << processedComponents++ << " with "
<< spt.number_of_vertices() << " vertices and " << spt.number_of_faces() << " faces" << std::endl;
bool clipResult = PMP::clip(spt, clp, CGAL::parameters::clip_volume(true).visitor(visitor));
status = status && clipResult;
if (!clipResult) {
std::cerr << "Warning: Clip operation failed for component " << processedComponents - 1 << std::endl;
}
}
std::cout << "Section mesh has " << section.number_of_vertices() << " vertices and "
<< section.number_of_faces() << " faces" << std::endl;
return status;
}
static bool GenerateUVCoord(CGALMesh& target)
{
if (target.is_empty()) return false;
std::vector<Point> points;
std::vector<std::vector<size_t>> polygons;
PMP::polygon_mesh_to_polygon_soup(target, points, polygons);
PMP::repair_polygon_soup(points, polygons);
PMP::orient_polygon_soup(points, polygons);
if (PMP::is_polygon_soup_a_polygon_mesh(polygons)) {
PMP::polygon_soup_to_polygon_mesh(points, polygons, target);
}
auto uvmap = target.add_property_map<VertexIndex, Kernel::Point_2>
("v:uv", Kernel::Point_2(0.0, 0.0)).first;
/*CGALMesh::Halfedge_index bh;
for (auto h : target.halfedges()) {
if (target.is_border(h)) {
bh = h;
break;
}
}*/
std::vector<CGALMesh::Halfedge_index> bhs;
PMP::extract_boundary_cycles(target, std::back_inserter(bhs));
//SMP::ARAP_parameterizer_3<CGALMesh> parameterizer;
SMP::LSCM_parameterizer_3<CGALMesh> parameterizer;
//SMP::Mean_value_coordinates_parameterizer_3<CGALMesh> parameterizer;
//SMP::Discrete_authalic_parameterizer_3<CGALMesh> parameterizer;
//SMP::Barycentric_mapping_parameterizer_3<CGALMesh> parameterizer;
//SMP::Discrete_conformal_map_parameterizer_3<CGALMesh> parameterizer;
//SMP::Iterative_authalic_parameterizer_3<CGALMesh> parameterizer;
//const unsigned int iterations = 15;
for (auto& bh : bhs) {
auto ecode = SMP::parameterize(target, parameterizer, bh, uvmap);
if (ecode != SMP::OK) {
cout << "Parameterize failed" << ecode << endl;
return false;
}
}
aiVector2D min(static_cast<ai_real>(std::numeric_limits<double>::max()));
aiVector2D max(static_cast<ai_real>(-std::numeric_limits<double>::max()));
for (auto pnt : uvmap) {
min.x = std::min(static_cast<ai_real>(min.x), static_cast<ai_real>(pnt.x()));
min.y = std::min(static_cast<ai_real>(min.y), static_cast<ai_real>(pnt.y()));
max.x = std::max(static_cast<ai_real>(max.x), static_cast<ai_real>(pnt.x()));
max.y = std::max(static_cast<ai_real>(max.y), static_cast<ai_real>(pnt.y()));
}
auto extent = max - min;
double length = std::max(static_cast<double>(extent.x), static_cast<double>(extent.y));
double step = 1.0 / length;
for (const auto vd : CGAL::vertices(target)) {
auto pnt = get(uvmap, vd);
double u = (static_cast<double>(pnt.x()) - static_cast<double>(min.x)) * step;
double v = (static_cast<double>(pnt.y()) - static_cast<double>(min.y)) * step;
put(uvmap, vd, Kernel::Point_2(u - std::floor(u), v - std::floor(v)));
}
return true;
}
static bool SaveAfterClipOriMesh_UVCoord(const MeshEntity& input, CGALMesh& clp, vector<MeshEntity>& out, bool texture) {
const CGALMesh& msh = get<0>(input);
if (msh.is_empty()) return false;
std::vector<CGALMesh> meshes;
PMP::split_connected_components(msh, meshes);
for (auto& spt : meshes) {
size_t nf = spt.number_of_faces();
if (nf < 2) continue;
bool is_out = is_outer_closed_boundary(spt);
if (is_out) continue;
if (!CGAL::is_closed(spt)) {
fnRepair(spt);
}
if (!CGAL::is_closed(spt)) {
continue;
}
PMP::clip(spt, clp, CGAL::parameters::clip_volume(false));
if (texture) {
GenerateUVCoord(spt);
}
out.push_back({ spt, get<1>(input), get<2>(input) });
}
return true;
}
/**
* 主函数 - 3D模型剖切程序入口
*
* 程序功能:
* 1. 读取剖切曲线文件
* 2. 生成剖切面
* 3. 读取3D模型
* 4. 执行剖切操作
* 5. 导出剖切面和剖切后的模型
*
* 命令行参数:
* argv[1] - 剖切曲线文件路径
* argv[2] - 输入模型文件路径
* argv[3] - 输出剖切面文件路径
* argv[4] - 输出剖切后模型文件路径
* argv[5] - 可选参数 "withTex" 表示生成纹理坐标
*/
int main(int argc, char** argv)
{
// 检查命令行参数数量
if (argc < 5) {
std::cerr << "Usage: " << argv[0] << " <clip_line_file> <model_file> <output_section> <output_clipped> [withTex]" << std::endl;
std::cerr << "参数说明:" << std::endl;
std::cerr << " clip_line_file: 剖切曲线文件(包含3D点坐标)" << std::endl;
std::cerr << " model_file: 输入3D模型文件" << std::endl;
std::cerr << " output_section: 输出剖切面文件" << std::endl;
std::cerr << " output_clipped: 输出剖切后模型文件" << std::endl;
std::cerr << " withTex: 可选参数,生成纹理坐标" << std::endl;
return 0;
}
// 解析命令行参数
fs::path ClipLine_inpath(argv[1]); // 剖切曲线文件路径
fs::path Model_inpath(argv[2]); // 输入模型文件路径
fs::path outpath1(argv[3]); // 输出剖切面文件路径
fs::path outpath2(argv[4]); // 输出剖切后模型文件路径
// 检查是否启用纹理坐标生成
bool withTex = (argc > 5 && strcmp(argv[5], "withTex") == 0);
//bool withTex = true; // 可以强制启用纹理坐标生成
// 步骤1:读取剖切曲线
std::cout << "Reading clip line from: " << ClipLine_inpath << std::endl;
vector<Point> curve = readCurve(ClipLine_inpath);
// 检查曲线是否有效
if (curve.empty()) {
std::cerr << "Error: No valid curve points found. Cannot proceed." << std::endl;
return 1;
}
// 步骤2:生成剖切面
std::cout << "Generating clip surface..." << std::endl;
CGALMesh clip = generateSurface1(curve); // 使用方法1生成剖切面
// 检查剖切面是否有效
if (clip.is_empty()) {
std::cerr << "Error: Generated clip surface is empty!" << std::endl;
return 1;
}
// 步骤3:读取输入模型
std::cout << "Reading model from: " << Model_inpath << std::endl;
std::vector<MeshEntity> ims; // 存储导入的网格实体
if (!ImportMeshes(Model_inpath, ims)) {
std::cerr << "Error: Failed to import model!" << std::endl;
return 1;
}
std::cout << "Imported " << ims.size() << " mesh entities" << std::endl;
// 步骤4:执行剖切操作
std::vector<MeshEntity> oms, ori; // oms存储剖切面,ori存储剖切后的模型
// 处理每个网格实体,生成剖切面
for (const auto& item : ims) {
CGALMesh msh = get<0>(item); // 获取网格
CGALMesh clp = clip; // 复制剖切面
CGALMesh outm; // 输出剖切面
std::cout << "Processing mesh: " << std::get<2>(item) << std::endl;
// 执行剖切操作
if (SectionClipping(msh, clp, outm)) {
if (!outm.is_empty()) {
std::cout << "Generated section with " << outm.number_of_vertices()
<< " vertices and " << outm.number_of_faces() << " faces" << std::endl;
// 如果需要,生成纹理坐标
if (withTex) {
GenerateUVCoord(outm);
}
// 将剖切面添加到输出列表
oms.push_back({ outm, std::get<1>(item), std::get<2>(item) });
} else {
std::cout << "Warning: Generated section is empty" << std::endl;
}
} else {
std::cout << "Warning: Section clipping failed" << std::endl;
}
}
// 处理每个网格实体,生成剖切后的模型
for (const auto& item : ims) {
CGALMesh clp = clip;
SaveAfterClipOriMesh_UVCoord(item, clp, ori, false);
}
/* 注释掉的合并代码
std::vector<MeshEntity> all;
all.reserve(oms.size() + ori.size());
all.insert(all.end(), oms.begin(), oms.end());
all.insert(all.end(), ori.begin(), ori.end());
*/
// 步骤5:导出结果
std::cout << "Exporting " << oms.size() << " section meshes to: " << outpath1 << std::endl;
ExportMeshes(outpath1, oms, withTex);
std::cout << "Exporting " << ori.size() << " clipped meshes to: " << outpath2 << std::endl;
ExportMeshes(outpath2, ori, false);
return 1; // 程序成功完成
}
最新发布