14.1 网格处理管道

网格处理管道建立在 Surface_mesh 数据结构之上,与 各向同性重网格化 密切相关。

0. 动机:为什么需要网格处理管道?

现实问题引入

想象你是一位3D打印工程师,收到了客户发来的模型文件:

  • 模型有50万个面,打印需要100小时
  • 表面有许多小孔洞,无法打印
  • 三角形质量差,有狭长的面片
  • 需要分成多个部分打印

或者你是一位游戏美术师:

  • 高模雕刻细节丰富,但无法在实时引擎中运行
  • 需要生成LOD(多细节层次)模型
  • 需要修复拓扑问题以便动画

传统方法的局限

手动处理

  • 耗时耗力,容易出错
  • 难以保持一致性
  • 无法处理大规模数据

单一工具

  • 只能解决特定问题
  • 缺乏流程整合
  • 难以自动化

管道化的优势

系统化:从输入到输出的完整流程 自动化:减少人工干预 可重复:相同输入得到相同输出 模块化:每个阶段可独立优化

如果不使用管道

  • 质量不稳定:每次处理结果不同
  • 效率低下:重复劳动,浪费时间
  • 错误累积:问题在下游放大
  • 难以维护:流程混乱,无法追踪

1. 直观理解:工厂流水线

1.1 “网格处理就像汽车装配”

想象一个汽车装配流水线:

阶段1:原材料检验

  • 检查零件是否合格
  • 剔除损坏的部件
  • 分类整理

阶段2:粗加工

  • 切割大体形状
  • 去除多余材料
  • 初步成型

阶段3:精加工

  • 打磨表面
  • 添加细节
  • 质量检查

阶段4:装配

  • 组装部件
  • 涂装
  • 最终检验

网格处理管道的对应

网格处理管道:

输入网格        简化/清理      修复/优化      细分/增强      输出网格
  │               │              │              │              │
  ▼               ▼              ▼              ▼              ▼
┌─────┐        ┌─────┐        ┌─────┐        ┌─────┐        ┌─────┐
│原始 │   →    │粗模 │   →    │净模 │   →    │精模 │   →    │最终 │
│数据 │        │简化 │        │修复 │        │增强 │        │产品 │
└─────┘        └─────┘        └─────┘        └─────┘        └─────┘
  │               │              │              │              │
  └─ 检测问题     └─ 减少复杂度   └─ 保证质量    └─ 提升细节    └─ 交付
     标记缺陷       保留特征       修复孔洞       添加细节       质量检验

1.2 “管道就像医院的体检流程”

挂号登记(输入):

  • 记录基本信息
  • 分配体检单

基础检查(清理):

  • 身高体重
  • 血压心率
  • 剔除明显异常

详细检查(修复):

  • 血液化验
  • 影像检查
  • 发现问题并治疗

康复增强(优化):

  • 营养补充
  • 运动指导
  • 定期复查

健康档案(输出):

  • 完整报告
  • 后续建议

1.3 “数据在管道中的旅程”

数据流可视化:

输入: 原始扫描网格
  │
  │  ┌─────────────────────────────────────┐
  │  │ 阶段1: 简化与清理                    │
  │  │ • 移除重复顶点                       │
  │  │ • 简化密集区域                       │
  │  │ • 删除退化三角形                     │
  │  └─────────────────────────────────────┘
  ▼
  顶点: 100,000 → 25,000
  面片: 200,000 → 50,000
  │
  │  ┌─────────────────────────────────────┐
  │  │ 阶段2: 修复与优化                    │
  │  │ • 填充孔洞                           │
  │  │ • 修复非流形边                       │
  │  │ • 平滑噪声                           │
  │  └─────────────────────────────────────┘
  ▼
  孔洞: 15 → 0
  非流形边: 23 → 0
  质量最差角度: 1.2° → 15°
  │
  │  ┌─────────────────────────────────────┐
  │  │ 阶段3: 细分与增强                    │
  │  │ • 自适应细分                         │
  │  │ • 特征保持                           │
  │  │ • 纹理坐标生成                       │
  │  └─────────────────────────────────────┘
  ▼
  面片: 50,000 → 120,000
  纹理坐标: 已生成
  UV展开: 完成
  │
  │  ┌─────────────────────────────────────┐
  │  │ 阶段4: 质量检验与输出                │
  │  │ • 几何检验                           │
  │  │ • 拓扑检验                           │
  │  │ • 格式转换                           │
  │  └─────────────────────────────────────┘
  ▼
输出: 可打印/可用的网格

2. 网格处理管道架构

2.1 标准四阶段管道

┌─────────────────────────────────────────────────────────────────┐
│                     网格处理管道                                 │
├─────────────┬─────────────┬─────────────┬───────────────────────┤
│   阶段1     │    阶段2    │    阶段3    │       阶段4           │
│ 简化与清理  │  修复与优化 │ 细分与增强  │    检验与输出         │
├─────────────┼─────────────┼─────────────┼───────────────────────┤
│ • 去重      │ • 孔洞填充  │ • 自适应细分│ • 几何检验            │
│ • 简化      │ • 边缝合    │ • 特征增强  │ • 拓扑检验            │
│ • 降噪      │ • 平滑      │ • 纹理生成  │ • 格式导出            │
│ • 裁剪      │ • 重网格化  │ • LOD生成   │ • 元数据记录          │
└─────────────┴─────────────┴─────────────┴───────────────────────┘

2.2 各阶段详细说明

阶段1:简化与清理(Simplification & Cleaning)

目标:减少数据量,移除明显错误

主要操作

  • 顶点去重:合并距离小于阈值的顶点
  • 面片简化:使用QEM算法减少面片数
  • 孤立组件移除:删除小连通分量
  • 退化面片删除:移除面积接近零的三角形

输入输出示例

输入: 原始扫描数据
  顶点: 500,000
  面片: 1,000,000
  问题: 大量重复点, 孤立噪声

处理:
  1. 去重: 0.01mm阈值
  2. 简化: 保留50%面片
  3. 清理: 移除面积<1e-6的面片

输出: 清理后数据
  顶点: 120,000
  面片: 240,000
  改善: 无重复, 无孤立点

阶段2:修复与优化(Repair & Optimization)

目标:修复几何和拓扑问题,提升质量

主要操作

  • 孔洞填充:检测并填充边界环
  • 非流形修复:拆分非流形边和顶点
  • 平滑去噪:保持特征的同时平滑噪声
  • 重网格化:改善三角形质量

输入输出示例

输入: 清理后数据
  孔洞: 12个
  非流形边: 5条
  非流形顶点: 2个
  最小角: 0.5°

处理:
  1. 填充孔洞: 使用相邻曲率
  2. 修复非流形: 拆分顶点
  3. 平滑: 10次迭代, 保持特征
  4. 重网格化: 目标边长2.0mm

输出: 修复后数据
  孔洞: 0个
  非流形: 0
  最小角: 18°

阶段3:细分与增强(Refinement & Enhancement)

目标:增加细节,准备最终输出

主要操作

  • 自适应细分:在曲率高处增加面片
  • 特征增强:锐化边缘和角点
  • 纹理坐标生成:UV展开和打包
  • LOD生成:创建多细节层次

输入输出示例

输入: 修复后数据
  面片: 240,000
  曲率变化: 平缓
  无纹理坐标

处理:
  1. 自适应细分: 曲率阈值0.1
  2. 特征增强: 锐化角度>60°的边
  3. UV展开: 最小化扭曲
  4. LOD生成: 高(100%), 中(50%), 低(25%)

输出: 增强后数据
  面片: 450,000
  纹理坐标: 已生成
  LOD: 3个级别

阶段4:检验与输出(Validation & Export)

目标:确保质量,转换为所需格式

主要操作

  • 几何检验:检查自相交、法向一致性
  • 拓扑检验:验证流形性、连通性
  • 格式导出:STL、OBJ、PLY等
  • 元数据记录:记录处理参数和统计

3. CGAL 实现详解

3.1 核心组件

CGAL 提供了管道各阶段所需的算法:

CGAL 网格处理管道组件:

简化与清理
├── Polygon_mesh_processing::remove_duplicate_points
├── Polygon_mesh_processing::remove_isolated_vertices
├── Surface_mesh_simplification::Edge_collapse_visitor
└── Polygon_mesh_processing::repair_polygon_soup

修复与优化
├── Polygon_mesh_processing::hole_filling
├── Polygon_mesh_processing::smooth_mesh
├── Polygon_mesh_processing::remesh
└── Polygon_mesh_processing::experimental::remove_self_intersections

细分与增强
├── Subdivision_method_3::CatmullClark_subdivision
├── Subdivision_method_3::Loop_subdivision
├── Polygon_mesh_processing::refine
└── Surface_mesh_parameterization 包

检验与输出
├── Polygon_mesh_processing::does_self_intersect
├── Polygon_mesh_processing::is_outward_oriented
└── CGAL::IO::write_OBJ/STL/OFF

3.2 完整管道代码示例

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing.h>
#include <CGAL/Surface_mesh_simplification/edge_collapse.h>
#include <CGAL/IO/OBJ.h>
#include <CGAL/IO/STL.h>
#include <iostream>
#include <vector>
#include <string>
 
namespace PMP = CGAL::Polygon_mesh_processing;
namespace SMS = CGAL::Surface_mesh_simplification;
 
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::Point_3 Point;
typedef CGAL::Surface_mesh<Point> Mesh;
 
// 网格统计信息
struct MeshStats {
    size_t vertices;
    size_t faces;
    size_t edges;
    size_t boundaries;
    double min_angle;
    double max_angle;
    double avg_quality;
    bool is_manifold;
    bool is_closed;
    bool has_self_intersections;
};
 
MeshStats analyze_mesh(const Mesh& mesh) {
    MeshStats stats;
    stats.vertices = mesh.number_of_vertices();
    stats.faces = mesh.number_of_faces();
    stats.edges = mesh.number_of_edges();
    
    // 计算边界数
    stats.boundaries = 0;
    for (auto e : mesh.edges()) {
        if (mesh.is_border(e)) stats.boundaries++;
    }
    
    // 计算角度统计
    stats.min_angle = 180.0;
    stats.max_angle = 0.0;
    double total_quality = 0.0;
    
    for (auto f : mesh.faces()) {
        auto he = mesh.halfedge(f);
        auto p0 = mesh.point(mesh.source(he));
        auto p1 = mesh.point(mesh.target(he));
        auto p2 = mesh.point(mesh.target(mesh.next(he)));
        
        // 计算三个角
        for (int i = 0; i < 3; ++i) {
            auto v1 = p1 - p0;
            auto v2 = p2 - p0;
            double angle = std::acos(v1 * v2 / (std::sqrt(v1.squared_length()) * 
                                                  std::sqrt(v2.squared_length()))) * 180.0 / M_PI;
            stats.min_angle = std::min(stats.min_angle, angle);
            stats.max_angle = std::max(stats.max_angle, angle);
            
            // 质量度量:等边三角形为1,退化时为0
            double quality = std::min({angle, 180.0 - angle}) / 60.0;
            total_quality += quality;
            
            // 轮换顶点
            std::swap(p0, p1);
            std::swap(p1, p2);
        }
    }
    
    stats.avg_quality = total_quality / (stats.faces * 3);
    stats.is_manifold = PMP::is_manifold(mesh);
    stats.is_closed = PMP::is_closed(mesh);
    stats.has_self_intersections = PMP::does_self_intersect(mesh);
    
    return stats;
}
 
void print_stats(const std::string& stage, const MeshStats& stats) {
    std::cout << "\n=== " << stage << " ===" << std::endl;
    std::cout << "Vertices: " << stats.vertices << std::endl;
    std::cout << "Faces: " << stats.faces << std::endl;
    std::cout << "Edges: " << stats.edges << std::endl;
    std::cout << "Boundary edges: " << stats.boundaries << std::endl;
    std::cout << "Min angle: " << stats.min_angle << "°" << std::endl;
    std::cout << "Max angle: " << stats.max_angle << "°" << std::endl;
    std::cout << "Avg quality: " << stats.avg_quality << std::endl;
    std::cout << "Manifold: " << (stats.is_manifold ? "yes" : "no") << std::endl;
    std::cout << "Closed: " << (stats.is_closed ? "yes" : "no") << std::endl;
    std::cout << "Self-intersections: " << (stats.has_self_intersections ? "yes" : "no") << std::endl;
}
 
// 阶段1: 简化与清理
void stage1_simplify_and_clean(Mesh& mesh, double target_edge_length) {
    std::cout << "\n[Stage 1] Simplification and Cleaning..." << std::endl;
    
    // 1.1 移除孤立顶点
    size_t removed = PMP::remove_isolated_vertices(mesh);
    std::cout << "  Removed " << removed << " isolated vertices" << std::endl;
    
    // 1.2 边折叠简化
    SMS::Count_stop_predicate<Mesh> stop(mesh.number_of_edges() / 2);
    SMS::edge_collapse(mesh, stop,
        CGAL::parameters::get_cost(SMS::Edge_length_cost<Mesh>())
                         .get_placement(SMS::Midpoint_placement<Mesh>())
    );
    std::cout << "  Simplified to " << mesh.number_of_edges() << " edges" << std::endl;
    
    // 1.3 清理退化面片
    std::vector<Mesh::Face_index> degenerate;
    for (auto f : mesh.faces()) {
        auto he = mesh.halfedge(f);
        auto p0 = mesh.point(mesh.source(he));
        auto p1 = mesh.point(mesh.target(he));
        auto p2 = mesh.point(mesh.target(mesh.next(he)));
        
        if (CGAL::collinear(p0, p1, p2)) {
            degenerate.push_back(f);
        }
    }
    
    for (auto f : degenerate) {
        mesh.remove_face(f);
    }
    mesh.collect_garbage();
    std::cout << "  Removed " << degenerate.size() << " degenerate faces" << std::endl;
}
 
// 阶段2: 修复与优化
void stage2_repair_and_optimize(Mesh& mesh, int smoothing_iterations) {
    std::cout << "\n[Stage 2] Repair and Optimization..." << std::endl;
    
    // 2.1 填充孔洞
    std::vector<Mesh::Halfedge_index> border_cycles;
    PMP::extract_boundary_cycles(mesh, std::back_inserter(border_cycles));
    
    std::cout << "  Found " << border_cycles.size() << " boundary cycles" << std::endl;
    
    for (auto h : border_cycles) {
        std::vector<Mesh::Face_index> patch;
        PMP::triangulate_hole(mesh, h, std::back_inserter(patch));
    }
    std::cout << "  Filled holes" << std::endl;
    
    // 2.2 平滑(保持特征)
    PMP::smooth_mesh(mesh, smoothing_iterations,
        CGAL::parameters::number_of_iterations(smoothing_iterations)
                         .use_area_smoothing(true)
                         .use_angle_smoothing(true)
                         .use_Delaunay_flips(true)
    );
    std::cout << "  Smoothed mesh (" << smoothing_iterations << " iterations)" << std::endl;
    
    // 2.3 重网格化
    PMP::isotropic_remeshing(
        mesh.faces(),
        2.0,  // 目标边长
        mesh,
        CGAL::parameters::number_of_iterations(3)
                         .protect_constraints(true)
    );
    std::cout << "  Remeshed" << std::endl;
}
 
// 阶段3: 细分与增强
void stage3_refine_and_enhance(Mesh& mesh, int subdivision_steps) {
    std::cout << "\n[Stage 3] Refinement and Enhancement..." << std::endl;
    
    // 3.1 自适应细分(基于曲率)
    // 这里使用简单的Loop细分作为示例
    // 实际应用中可以根据曲率自适应细分
    if (subdivision_steps > 0) {
        CGAL::Subdivision_method_3::Loop_subdivision(mesh, subdivision_steps);
        std::cout << "  Subdivided (" << subdivision_steps << " steps)" << std::endl;
    }
    
    // 3.2 计算法向
    mesh.add_property_map<Mesh::Vertex_index, Kernel::Vector_3>("v:normal");
    PMP::compute_vertex_normals(mesh,
        CGAL::parameters::vertex_normal_map(mesh.property_map<Mesh::Vertex_index, Kernel::Vector_3>("v:normal").first)
    );
    std::cout << "  Computed vertex normals" << std::endl;
}
 
// 阶段4: 检验与输出
bool stage4_validate_and_export(const Mesh& mesh, const std::string& output_prefix) {
    std::cout << "\n[Stage 4] Validation and Export..." << std::endl;
    
    bool success = true;
    
    // 4.1 几何检验
    if (PMP::does_self_intersect(mesh)) {
        std::cerr << "  WARNING: Mesh has self-intersections\!" << std::endl;
        success = false;
    } else {
        std::cout << "  ✓ No self-intersections" << std::endl;
    }
    
    // 4.2 拓扑检验
    if (\!PMP::is_manifold(mesh)) {
        std::cerr << "  WARNING: Mesh is not manifold\!" << std::endl;
        success = false;
    } else {
        std::cout << "  ✓ Mesh is manifold" << std::endl;
    }
    
    // 4.3 法向一致性
    if (\!PMP::is_outward_oriented(mesh)) {
        std::cout << "  Reorienting faces..." << std::endl;
        // 注意:这里需要非const引用,实际实现中可能需要拷贝
    } else {
        std::cout << "  ✓ Faces outward oriented" << std::endl;
    }
    
    // 4.4 导出多种格式
    std::string obj_file = output_prefix + ".obj";
    std::string stl_file = output_prefix + ".stl";
    std::string off_file = output_prefix + ".off";
    
    CGAL::IO::write_OBJ(obj_file, mesh);
    std::cout << "  Exported: " << obj_file << std::endl;
    
    CGAL::IO::write_STL(stl_file, mesh);
    std::cout << "  Exported: " << stl_file << std::endl;
    
    CGAL::IO::write_OFF(off_file, mesh);
    std::cout << "  Exported: " << off_file << std::endl;
    
    return success;
}
 
// 主处理流程
int main(int argc, char* argv[])
{
    // 参数解析
    std::string input_file = (argc > 1) ? argv[1] : "input.obj";
    std::string output_prefix = (argc > 2) ? argv[2] : "output";
    
    // 配置参数
    double target_edge_length = (argc > 3) ? std::stod(argv[3]) : 2.0;
    int smoothing_iterations = (argc > 4) ? std::stoi(argv[4]) : 5;
    int subdivision_steps = (argc > 5) ? std::stoi(argv[5]) : 1;
    
    std::cout << "========================================" << std::endl;
    std::cout << "CGAL Mesh Processing Pipeline" << std::endl;
    std::cout << "========================================" << std::endl;
    std::cout << "Input: " << input_file << std::endl;
    std::cout << "Output prefix: " << output_prefix << std::endl;
    std::cout << "Target edge length: " << target_edge_length << std::endl;
    std::cout << "Smoothing iterations: " << smoothing_iterations << std::endl;
    std::cout << "Subdivision steps: " << subdivision_steps << std::endl;
    
    // 读取输入
    Mesh mesh;
    if (\!CGAL::IO::read_OBJ(input_file, mesh)) {
        std::cerr << "Failed to read input file: " << input_file << std::endl;
        return 1;
    }
    
    std::cout << "\nInput mesh loaded successfully" << std::endl;
    
    // 初始分析
    MeshStats stats_initial = analyze_mesh(mesh);
    print_stats("Initial Mesh", stats_initial);
    
    // 阶段1: 简化与清理
    stage1_simplify_and_clean(mesh, target_edge_length);
    MeshStats stats_stage1 = analyze_mesh(mesh);
    print_stats("After Stage 1", stats_stage1);
    
    // 阶段2: 修复与优化
    stage2_repair_and_optimize(mesh, smoothing_iterations);
    MeshStats stats_stage2 = analyze_mesh(mesh);
    print_stats("After Stage 2", stats_stage2);
    
    // 阶段3: 细分与增强
    stage3_refine_and_enhance(mesh, subdivision_steps);
    MeshStats stats_stage3 = analyze_mesh(mesh);
    print_stats("After Stage 3", stats_stage3);
    
    // 阶段4: 检验与输出
    bool success = stage4_validate_and_export(mesh, output_prefix);
    MeshStats stats_final = analyze_mesh(mesh);
    print_stats("Final Mesh", stats_final);
    
    // 总结
    std::cout << "\n========================================" << std::endl;
    std::cout << "Processing Summary" << std::endl;
    std::cout << "========================================" << std::endl;
    std::cout << "Vertices: " << stats_initial.vertices << " → " << stats_final.vertices << std::endl;
    std::cout << "Faces: " << stats_initial.faces << " → " << stats_final.faces << std::endl;
    std::cout << "Min angle: " << stats_initial.min_angle << "° → " << stats_final.min_angle << "°" << std::endl;
    std::cout << "Avg quality: " << stats_initial.avg_quality << " → " << stats_final.avg_quality << std::endl;
    std::cout << "Status: " << (success ? "SUCCESS" : "WARNING") << std::endl;
    
    return success ? 0 : 1;
}

4. 实际应用案例

4.1 案例一:3D打印准备

场景:将扫描得到的文物模型准备用于3D打印。

需求

  • 封闭无孔洞
  • 无自相交
  • 合理的面片数(<100万)
  • 良好的三角形质量

管道配置

// 3D打印优化管道
void prepare_for_3d_printing(const std::string& input, const std::string& output) {
    Mesh mesh;
    CGAL::IO::read_OBJ(input, mesh);
    
    // 阶段1: 简化到50万面以下
    if (mesh.number_of_faces() > 500000) {
        SMS::Count_stop_predicate<Mesh> stop(500000);
        SMS::edge_collapse(mesh, stop);
    }
    
    // 阶段2: 必须封闭
    if (\!PMP::is_closed(mesh)) {
        PMP::triangulate_hole(mesh, ...);  // 填充所有孔洞
    }
    
    // 修复自相交
    if (PMP::does_self_intersect(mesh)) {
        PMP::experimental::remove_self_intersections(mesh);
    }
    
    // 阶段3: 轻微细分保证平滑
    CGAL::Subdivision_method_3::Loop_subdivision(mesh, 1);
    
    // 阶段4: 导出STL
    CGAL::IO::write_STL(output, mesh);
}

4.2 案例二:游戏LOD生成

场景:为游戏模型生成多细节层次。

管道配置

// LOD生成管道
void generate_lods(const std::string& input, const std::string& output_prefix) {
    Mesh mesh;
    CGAL::IO::read_OBJ(input, mesh);
    
    // LOD 0: 高细节 (100%)
    CGAL::IO::write_OBJ(output_prefix + "_lod0.obj", mesh);
    
    // LOD 1: 中细节 (50%)
    Mesh lod1 = mesh;
    SMS::Count_stop_predicate<Mesh> stop1(mesh.number_of_edges() * 0.5);
    SMS::edge_collapse(lod1, stop1);
    CGAL::IO::write_OBJ(output_prefix + "_lod1.obj", lod1);
    
    // LOD 2: 低细节 (25%)
    Mesh lod2 = mesh;
    SMS::Count_stop_predicate<Mesh> stop2(mesh.number_of_edges() * 0.25);
    SMS::edge_collapse(lod2, stop2);
    CGAL::IO::write_OBJ(output_prefix + "_lod2.obj", lod2);
    
    // LOD 3: 极低细节 (10%)
    Mesh lod3 = mesh;
    SMS::Count_stop_predicate<Mesh> stop3(mesh.number_of_edges() * 0.10);
    SMS::edge_collapse(lod3, stop3);
    CGAL::IO::write_OBJ(output_prefix + "_lod3.obj", lod3);
}

4.3 案例三:医学图像处理

场景:从CT扫描重建骨骼模型并优化。

管道配置

// 医学模型处理管道
void process_medical_model(const std::string& input, const std::string& output) {
    Mesh mesh;
    CGAL::IO::read_OFF(input, mesh);  // 从MC算法读取
    
    // 阶段1: 去除MC算法产生的噪声
    PMP::smooth_mesh(mesh, 10);  // 多次平滑
    
    // 阶段2: 修复拓扑(骨骼应该封闭)
    while (\!PMP::is_closed(mesh)) {
        PMP::triangulate_hole(mesh, ...);
    }
    
    // 阶段3: 保持特征的细化
    PMP::isotropic_remeshing(mesh.faces(), 0.5, mesh,
        CGAL::parameters::protect_constraints(true)  // 保护解剖特征
    );
    
    // 阶段4: 导出为医学格式
    CGAL::IO::write_STL(output, mesh);
}

5. 参数调优指南

5.1 根据应用场景选择参数

应用场景阶段1阶段2阶段3阶段4
3D打印简化到<100万面必须封闭轻微细分STL格式
游戏LOD激进简化保持特征不细分多级别输出
可视化适度简化平滑噪声纹理坐标OBJ格式
仿真分析保持细节高质量网格各向同性导出CFD格式
存档不简化修复为主不细分多格式备份

5.2 质量检查清单

处理前检查:
□ 文件格式是否正确
□ 坐标系统是否已知
□ 是否有纹理/颜色信息需要保留
□ 目标应用场景

处理中检查:
□ 每阶段后验证拓扑
□ 监控面片数量变化
□ 检查特征保留情况
□ 内存使用情况

处理后检查:
□ 无自相交
□ 法向一致
□ 无退化面片
□ 目标格式验证

6. 故障排除

6.1 常见问题与解决

问题1:简化后特征丢失

  • 原因:简化算法未保护特征边
  • 解决:使用 protect_constraints(true) 选项

问题2:孔洞填充失败

  • 原因:孔洞太大或形状复杂
  • 解决:分多次填充,或手动干预

问题3:平滑后变形

  • 原因:平滑迭代次数过多
  • 解决:减少迭代次数,或使用特征保持平滑

问题4:导出后文件损坏

  • 原因:非流形几何
  • 解决:确保所有阶段后都检查流形性

参考文献

  1. Botsch, M., Kobbelt, L., Pauly, M., Alliez, P., & Lévy, B. (2010). Polygon Mesh Processing. CRC Press.

  2. CGAL Documentation: Polygon Mesh Processing. https://doc.cgal.org/latest/Polygon_mesh_processing/

  3. CGAL Documentation: Surface Mesh Simplification. https://doc.cgal.org/latest/Surface_mesh_simplification/

  4. Garland, M., & Heckbert, P. S. (1997). Surface simplification using quadric error metrics. In Proceedings of SIGGRAPH (pp. 209-216).