第25章 几何求值(Geometry Evaluation):参数化、法向量与曲率
25.1 学习目标
- 理解曲面参数化(Surface Parametrization)的概念:CAD 几何中每个曲面自带 (u,v) 参数坐标系,掌握
getParametrization()在物理坐标与参数坐标之间建立双向映射 - 掌握通过
getNormal()获取曲面上任意参数坐标处的单位法向量(Unit Normal),理解其与(dim,tag)实体对 API(如getCurvature)在参数签名上的关键区别 - 学会使用
getPrincipalCurvatures()获取主曲率(Principal Curvatures)及其主方向(Principal Directions),理解曲率在自适应网格细化中的判据作用 - 掌握曲线求值 API 族:
getValue()(坐标)、getDerivative()(切向量)、getSecondDerivative()(二阶导),以及reparametrizeOnSurface()曲线-曲面联合求值 - 了解上述 API 在 CAE 中的典型应用:压力载荷施加(需法向)、自适应网格细化(需曲率)、边界条件位置计算(需参数化)
25.2 核心概念说明
25.2.1 曲面参数化(Surface Parametrization)
在 CAD 内核中,每张曲面(Surface)都定义了一个从二维参数域 (u,v) 到三维物理空间 (x,y,z) 的映射:
S(u,v) = (x(u,v), y(u,v), z(u,v))
其中 (u,v) 属于参数定义域 [u_min, u_max] x [v_min, v_max]
参数域范围因几何类型而异:平面和裁剪曲面(Trimmed Surface)的参数域通常受裁剪边界限制,球面和圆柱面等周期曲面则可能覆盖 [0, 2*pi] 范围。
Gmsh 通过两个 API 实现物理坐标与参数坐标的双向转换:
| 方向 | API | 输入 | 输出 |
|---|---|---|---|
| 物理 -> 参数 | getParametrization(dim, tag, coord, paramCoord) |
(x,y,z) | (u,v) 或 t |
| 参数 -> 物理 | getValue(dim, tag, paramCoord, coord) |
(u,v) 或 t | (x,y,z) |
25.2.2 法向量(Normal)与曲率(Curvature)
曲面上一点的法向量和曲率是 CAE 前处理中最重要的微分几何量:
- 法向量(Normal):垂直于曲面切平面的单位向量。在有限元分析中用于施加压力载荷(压力沿法向作用)、接触检测、壳单元局部坐标系建立。
- 主曲率(Principal Curvatures):曲面上一点沿不同方向的弯曲程度不同,存在一对正交方向使弯曲程度达到极值,分别称为最大主曲率
k1和最小主曲率k2。 - 高斯曲率(Gaussian Curvature)
K = k1 * k2:判断曲面局部形状(K>0 椭圆点, K<0 双曲点, K=0 抛物点) - 平均曲率(Mean Curvature)
H = (k1 + k2) / 2:用于曲率自适应网格细化判据
API 签名关键差异:Gmsh API 中存在不一致——getNormal() 和 getPrincipalCurvatures() 仅接收曲面标签(tag),而 getCurvature() 接收 (dim, tag) 对。这是 Gmsh API 的历史遗留问题,使用时务必注意。
25.2.3 曲线求值(Curve Evaluation)
对于一维曲线实体,Gmsh 提供三个层次的求值 API:
| API | 返回 | 数学含义 | 典型用途 |
|---|---|---|---|
getValue(dim, tag, t, xyz) |
坐标 | C(t) = (x,y,z) |
边界条件位置计算 |
getDerivative(dim, tag, t, deriv) |
切向量 | C'(t) = (dx/dt, dy/dt, dz/dt) |
切向载荷、流线方向 |
getSecondDerivative(dim, tag, t, deriv2) |
二阶导 | C''(t) |
曲率计算、弯管应力估算 |
25.2.4 CAE 应用场景
- 压力载荷施加:
p * n需要曲面上每个积分点的单位法向量n。从网格节点获取参数坐标后,通过getNormal()获取精确的 CAD 法向(而非网格近似法向),避免网格粗糙时法向误差导致的载荷方向偏差。 - 自适应网格细化:在曲率大的区域(如倒角根部、小孔边缘)加密网格。通过
getPrincipalCurvatures()评估局部曲率,设定曲率阈值触发局部细化。 - 边界条件位置计算:利用
getValue()在曲线参数坐标上精确计算约束施加点或载荷施加点的物理坐标,适用于参数化边界条件脚本。
25.3 C++ 代码逐段讲解
25.3.1 头文件与初始化
#include <set>
#include <vector>
#include <cmath>
#include <numeric>
#include <gmsh.h>
int main(int argc, char **argv)
{
gmsh::initialize(argc, argv);
gmsh::model::add("ch25");
解释:<numeric> 用于 std::iota 等序列生成,<cmath> 提供 std::sqrt、std::abs 等数学函数。gmsh::model::add("ch25") 为模型命名。
25.3.2 创建融合几何体并生成曲面网格
// ---- 1. 创建球与立方体的融合几何 ----
int s = gmsh::model::occ::addSphere(0, 0, 0, 1); // 单位球
int b = gmsh::model::occ::addBox(0.5, 0, 0, 1.3, 2, 3); // 与球相交的盒子
std::vector<std::pair<int, int>> ov;
std::vector<std::vector<std::pair<int, int>>> ovv;
gmsh::model::occ::fuse({{3, s}}, {{3, b}}, ov, ovv);
gmsh::model::occ::synchronize();
// 生成二维面网格(仅曲面)
gmsh::model::mesh::generate(2);
解释:球和长方体做布尔融合(Boolean Union),产生一个既有球面区域又有平面区域的复合几何体。这种"曲面+平面"的组合最能体现法向量和曲率 API 的价值:球面区域的法向量处处变化且曲率非零,平面区域法向量恒定且曲率为零。mesh::generate(2) 仅生成面网格,不生成体网格,以节省计算量。
25.3.3 获取曲面网格节点及其参数坐标
// ---- 2. 遍历所有面,获取网格节点及参数坐标 ----
std::vector<std::pair<int, int>> entities;
gmsh::model::getEntities(entities, 2); // dim=2: 面
std::vector<double> allNormals; // 用于后处理显示: 法向量
std::vector<double> allCurvatures; // 用于后处理显示: 最大主曲率
for(auto e : entities) {
int surfaceTag = e.second;
// 获取面上的网格节点
// includeBoundary=true: 边界节点会被重新参数化到当前面上
std::vector<std::size_t> nodeTags;
std::vector<double> coord, paramCoord;
gmsh::model::mesh::getNodes(nodeTags, coord, paramCoord,
2, surfaceTag, true);
解释:getNodes() 的最后一个参数 includeBoundary=true 至关重要。内部节点(interior nodes)存储了参数坐标,但边界节点(boundary nodes)属于多个面共有——当设为 true 时,Gmsh 会将边界节点重新参数化(reparametrize)到当前面上,从而获得在该面上的 (u,v) 坐标。如果设为 false,则边界节点的 paramCoord 为空。
coord 的数据布局为 [x1,y1,z1, x2,y2,z2, ...],paramCoord 的布局为 [u1,v1, u2,v2, ...],两者按节点一一对应。
25.3.4 获取曲面法向量
// ---- 3. 获取面上各节点参数坐标处的精确法向量 ----
std::vector<double> normals;
gmsh::model::getNormal(surfaceTag, paramCoord, normals);
// 注意: getNormal() 的第一个参数是曲面标签(tag),不是(dim,tag)对
// 这与 getCurvature(dim, tag, ...) 的签名不同!
解释:getNormal(tag, parametricCoord, normals) —— 第一个参数是面标签(整数),不是 (dim, tag) 对。这是 Gmsh API 中的关键不一致点,也是 CAE 开发者最常见的错误来源。
normals 返回的单位法向量布局为 [nx1,ny1,nz1, nx2,ny2,nz2, ...],与输入的 paramCoord 按点一一对应。法向量的方向遵循右手定则(基于曲面的 (u,v) 参数化方向 Su x Sv)。
这里的法向量是直接从 CAD 几何求值得到的精确法向,而非从网格三角面片计算的法向近似值。在压力载荷施加场景中,使用 getNormal() 的法向可以避免网格粗糙时法向偏差导致的载荷方向错误。
25.3.5 获取曲面主曲率
// ---- 4. 获取主曲率及主方向 ----
std::vector<double> curvMax, curvMin, dirMax, dirMin;
gmsh::model::getPrincipalCurvatures(surfaceTag, paramCoord,
curvMax, curvMin, dirMax, dirMin);
// getPrincipalCurvatures(tag, ...) 同样仅使用面标签,不使用(dim,tag)对
解释:getPrincipalCurvatures(tag, parametricCoord, curvatureMax, curvatureMin, directionMax, directionMin) 在一次调用中返回四个向量:
| 输出向量 | 长度 (N个点) | 内容 |
|---|---|---|
curvatureMax |
N | 每个点的最大主曲率 k1(正值=凸,负值=凹) |
curvatureMin |
N | 每个点的最小主曲率 k2 |
directionMax |
3N | k1 对应的主方向单位向量 [dx1,dy1,dz1, dx2,dy2,dz2, ...] |
directionMin |
3N | k2 对应的主方向单位向量 |
两个主方向始终正交。对于球面,k1 = k2 = 1/R(脐点,任意两个正交方向都可视为主方向)。对于平面,k1 = k2 = 0。对于圆柱面,k1 = 1/R(周向),k2 = 0(轴向)。
注意:还有一个简化 API getCurvature(dim, tag, parametricCoord, curvatures),它只返回每个点的最大曲率(单值),且使用 (dim, tag) 对作为前两个参数——与 getPrincipalCurvatures 的签名风格不同。
25.3.6 整理法向量与曲率数据用于后处理显示
// ---- 5. 整理数据用于后处理视图 ----
for(std::size_t i = 0; i < coord.size(); i += 3) {
std::size_t idx = i / 3; // 点索引
// 法向量视图: 位置 + 法向量 (VP = Vector on Points, 6个double/点)
allNormals.insert(allNormals.end(), {
coord[i], coord[i+1], coord[i+2], // x, y, z
normals[i], normals[i+1], normals[i+2] // nx, ny, nz
});
// 曲率视图: 位置 + 标量值 (SP = Scalar on Points, 4个double/点)
allCurvatures.insert(allCurvatures.end(), {
coord[i], coord[i+1], coord[i+2], // x, y, z
curvMax[idx] // 最大主曲率
});
}
} // end for each surface
解释:将数据组织为 list-based 后处理视图格式:
- "VP" (Vector on Points):每个数据条目 6 个 double — (x,y,z, vx,vy,vz)
- "SP" (Scalar on Points):每个数据条目 4 个 double — (x,y,z, scalar)
这里选择了最大主曲率 curvMax 作为标量场显示;也可以选择平均曲率 (curvMax + curvMin) / 2 或高斯曲率 curvMax * curvMin 作为细化判据。
25.3.7 创建后处理视图
// ---- 6. 创建法向量矢量视图 ----
int vn = gmsh::view::add("Surface normals");
gmsh::view::addListData(vn, "VP", allNormals.size() / 6, allNormals);
gmsh::view::option::setNumber(vn, "ShowScale", 0); // 不显示色标
gmsh::view::option::setNumber(vn, "ArrowSizeMax", 30); // 箭头最大 30 像素
gmsh::view::option::setNumber(vn, "ColormapNumber", 19);
// ---- 7. 创建曲率标量视图 ----
int vc = gmsh::view::add("Principal curvatures (max)");
gmsh::view::addListData(vc, "SP", allCurvatures.size() / 4, allCurvatures);
gmsh::view::option::setNumber(vc, "ShowScale", 1);
gmsh::view::option::setNumber(vc, "IntervalsType", 2); // 连续色带
解释:addListData() 创建独立于网格的 list-based 视图。"VP" 格式将法向量渲染为带箭头的矢量线;"SP" 格式将曲率值渲染为着色点云。这些视图可在 Gmsh GUI 中独立开关、叠加显示。
25.3.8 曲线参数化边界与求值
// ---- 8. 获取某条曲线的参数化边界并等距采样求值 ----
// 以曲线 5 为例(融合后自动生成的边)
std::vector<double> tMin, tMax;
gmsh::model::getParametrizationBounds(1, 5, tMin, tMax);
// 对于曲线(dim=1): tMin/tMax 各含1个值,分别为参数 t 的下界和上界
int N = 15; // 采样点数
std::vector<double> tSamples, xyz1;
for(int i = 0; i < N; i++) {
double t = tMin[0] + i * (tMax[0] - tMin[0]) / (N - 1);
tSamples.push_back(t);
}
gmsh::model::getValue(1, 5, tSamples, xyz1);
// xyz1 布局: [x1,y1,z1, x2,y2,z2, ...], 共 N 个点
解释:getParametrizationBounds(1, 5, tMin, tMax) 返回曲线参数 t 的定义域 [t_min, t_max]。对于 dim=1 的曲线,tMin 和 tMax 各为一个元素的向量;对于 dim=2 的曲面,tMin=[u_min, v_min],tMax=[u_max, v_max]。
getValue(1, 5, tSamples, xyz1) 在曲线上等距采样 15 个参数点,计算对应的三维坐标。输出的 xyz1 包含 3*N 个 double。
25.3.9 曲线导数与二阶导数
// ---- 9. 获取曲线在采样点的切向量(一阶导)和二阶导 ----
std::vector<double> deriv1, deriv2;
gmsh::model::getDerivative(1, 5, tSamples, deriv1);
// deriv1 布局: [dx/dt, dy/dt, dz/dt, ...], 每点 3 个分量
gmsh::model::getSecondDerivative(1, 5, tSamples, deriv2);
// deriv2 布局: [d2x/dt2, d2y/dt2, d2z/dt2, ...], 每点 3 个分量
// 计算第一个采样点处的切向量模长和曲线曲率
double dx = deriv1[0], dy = deriv1[1], dz = deriv1[2];
double ds = std::sqrt(dx*dx + dy*dy + dz*dz); // 切向量模长 = |C'(t)|
double ddx = deriv2[0], ddy = deriv2[1], ddz = deriv2[2];
// 曲线曲率: kappa = |C' x C''| / |C'|^3
double crossX = dy*ddz - dz*ddy;
double crossY = dz*ddx - dx*ddz;
double crossZ = dx*ddy - dy*ddx;
double kappa = std::sqrt(crossX*crossX + crossY*crossY + crossZ*crossZ)
/ (ds * ds * ds);
gmsh::logger::write("Curve 5 curvature at t[0] = " + std::to_string(kappa));
解释:getDerivative() 返回的是参数导数 dC/dt(非弧长导数)。切向量方向可直接用于局部坐标系对齐或切向载荷方向定义。结合 getDerivative() 和 getSecondDerivative() 可计算曲线的 Frenet 曲率:
kappa = |C'(t) x C''(t)| / |C'(t)|^3
这在 CAE 中可用于:评估弯管处的应力集中系数(曲率越大,弯管应力集中越显著)、判断曲线网格加密区域。
25.3.10 曲线在曲面上的重参数化(Reparametrization)
// ---- 10. 将曲线重参数化到曲面上,验证一致性 ----
// 将曲线 5 上的采样点重新参数化到面 1 上
std::vector<double> uvOnSurface, xyz2;
gmsh::model::reparametrizeOnSurface(1, 5, tSamples, 1, uvOnSurface);
// uvOnSurface 布局: [u1,v1, u2,v2, ...] — 曲线采样点在面1上的(u,v)坐标
// 用面 1 的参数坐标重新求值
gmsh::model::getValue(2, 1, uvOnSurface, xyz2);
// 验证两种求值方式得到相同结果
double maxDiff = 0.0;
for(std::size_t i = 0; i < xyz1.size(); i++)
maxDiff = std::max(maxDiff, std::abs(xyz1[i] - xyz2[i]));
if(maxDiff < 1e-12)
gmsh::logger::write("Curve & surface evaluation match! (max diff < 1e-12)");
else
gmsh::logger::write("Mismatch: max diff = " + std::to_string(maxDiff), "warning");
解释:reparametrizeOnSurface(dim, tag, paramCoord, surfaceTag, surfaceParamCoord) 实现了一条曲线(或其上的点)在指定曲面上的重新参数化。该功能仅在部分 CAD 内核中可用。
这里验证了两种求值路径的一致性:
1. 直接在曲线 5 上求值 → xyz1
2. 将曲线 5 的参数坐标重参数化到面 1 → 在面 1 上求值 → xyz2
两者应得到相同的物理坐标(因为曲线 5 是面 1 的边界)。这种验证是 CAE 工作流中的良好实践——在边界条件施加时,如果曲线和曲面求值不一致,说明 CAD 模型可能存在几何不兼容问题(如边和面之间有微小间隙)。
25.3.11 物理坐标到参数坐标的反向映射
// ---- 11. 演示物理坐标 -> 参数坐标的反向映射 ----
// 取第一个面(面 1)上网格的第一个节点,演示反向参数化
std::vector<std::size_t> nTags;
std::vector<double> nCoord, nParam;
gmsh::model::mesh::getNodes(nTags, nCoord, nParam, 2, 1, true);
if(nCoord.size() >= 3) {
// 取第一个节点坐标求其参数坐标
std::vector<double> singleCoord = {nCoord[0], nCoord[1], nCoord[2]};
std::vector<double> fromCoord;
gmsh::model::getParametrization(2, 1, singleCoord, fromCoord);
// fromCoord[0] = u, fromCoord[1] = v — 该节点在面1上的参数坐标
gmsh::logger::write(
"Node " + std::to_string(nTags[0]) +
" at (" + std::to_string(nCoord[0]) + "," +
std::to_string(nCoord[1]) + "," +
std::to_string(nCoord[2]) + ")" +
" -> (u=" + std::to_string(fromCoord[0]) +
", v=" + std::to_string(fromCoord[1]) + ")");
}
解释:getParametrization(dim, tag, coord, parametricCoord) 实现物理坐标到参数坐标的反向映射。输入物理坐标 (x,y,z),输出参数坐标 (u,v)(曲面)或 t(曲线)。这个方向通常比正向映射(参数→物理)更复杂,因为需要求解非线性反问题。对于距离曲面较远的点,可能无法正确映射或返回近似值。
CAE 应用:在接触分析中,从节点(slave node)投影到主面(master surface)上以计算穿透量和接触力时,需要反向参数化。
25.3.12 GUI 启动
// ---- 12. 启动 GUI 查看结果 ----
std::set<std::string> args(argv, argv + argc);
if(!args.count("-nopopup")) gmsh::fltk::run();
gmsh::finalize();
return 0;
}
解释:-nopopup 命令行参数支持无头批处理模式。在 GUI 模式下,法向量视图(箭头)和曲率视图(着色点云)可叠加在网格上查看。
25.4 完整可运行代码
// =============================================================================
// Chapter 25: Geometry Evaluation — Parametrization, Normals & Curvatures
//
// 功能: 演示 Gmsh 几何求值 API 的完整用法,包括:
// 曲面参数化、法向量计算、主曲率提取、曲线求值与导数、
// 曲线-曲面重参数化、物理-参数坐标双向映射
//
// 编译 (需链接 Gmsh SDK):
// g++ -o ch25_geometry_eval ch25_geometry_eval.cpp -lgmsh -std=c++11
//
// 运行:
// ./ch25_geometry_eval (带 GUI 交互查看法向量和曲率)
// ./ch25_geometry_eval -nopopup (仅运行计算,不启动 GUI)
// =============================================================================
#include <set>
#include <vector>
#include <cmath>
#include <string>
#include <algorithm>
#include <gmsh.h>
int main(int argc, char **argv)
{
gmsh::initialize(argc, argv);
gmsh::model::add("ch25");
// ===== 1. 创建球与立方体的融合几何 =====
int s = gmsh::model::occ::addSphere(0, 0, 0, 1);
int b = gmsh::model::occ::addBox(0.5, 0, 0, 1.3, 2, 3);
std::vector<std::pair<int, int>> ov;
std::vector<std::vector<std::pair<int, int>>> ovv;
gmsh::model::occ::fuse({{3, s}}, {{3, b}}, ov, ovv);
gmsh::model::occ::synchronize();
// 生成二维面网格
gmsh::model::mesh::generate(2);
// ===== 2. 遍历所有面,获取法向量和主曲率 =====
std::vector<std::pair<int, int>> entities;
gmsh::model::getEntities(entities, 2);
std::vector<double> allNormals;
std::vector<double> allCurvatures;
for(auto e : entities) {
int surfaceTag = e.second;
// 获取面上的网格节点(含边界节点重参数化)
std::vector<std::size_t> nodeTags;
std::vector<double> coord, paramCoord;
gmsh::model::mesh::getNodes(nodeTags, coord, paramCoord,
2, surfaceTag, true);
// 获取精确法向量 (tag 仅用面标签,非 (dim,tag) 对)
std::vector<double> normals;
gmsh::model::getNormal(surfaceTag, paramCoord, normals);
// 获取主曲率 (tag 仅用面标签)
std::vector<double> curvMax, curvMin, dirMax, dirMin;
gmsh::model::getPrincipalCurvatures(surfaceTag, paramCoord,
curvMax, curvMin, dirMax, dirMin);
// 整理为后处理视图数据格式
for(std::size_t i = 0; i < coord.size(); i += 3) {
std::size_t idx = i / 3;
// VP: Vector on Points (6 doubles per point)
allNormals.insert(allNormals.end(), {
coord[i], coord[i+1], coord[i+2],
normals[i], normals[i+1], normals[i+2]
});
// SP: Scalar on Points (4 doubles per point)
allCurvatures.insert(allCurvatures.end(), {
coord[i], coord[i+1], coord[i+2],
curvMax[idx]
});
}
}
// ===== 3. 创建后处理视图 =====
int vn = gmsh::view::add("Surface normals");
gmsh::view::addListData(vn, "VP", allNormals.size() / 6, allNormals);
gmsh::view::option::setNumber(vn, "ShowScale", 0);
gmsh::view::option::setNumber(vn, "ArrowSizeMax", 30);
gmsh::view::option::setNumber(vn, "ColormapNumber", 19);
int vc = gmsh::view::add("Principal curvatures (max)");
gmsh::view::addListData(vc, "SP", allCurvatures.size() / 4, allCurvatures);
gmsh::view::option::setNumber(vc, "ShowScale", 1);
gmsh::view::option::setNumber(vc, "IntervalsType", 2);
// ===== 4. 曲线参数化边界与等距采样求值 =====
std::vector<double> tMin, tMax;
gmsh::model::getParametrizationBounds(1, 5, tMin, tMax);
int N = 15;
std::vector<double> tSamples, xyz1;
for(int i = 0; i < N; i++) {
double t = tMin[0] + i * (tMax[0] - tMin[0]) / (N - 1);
tSamples.push_back(t);
}
gmsh::model::getValue(1, 5, tSamples, xyz1);
// ===== 5. 曲线切向量与二阶导数 =====
std::vector<double> deriv1, deriv2;
gmsh::model::getDerivative(1, 5, tSamples, deriv1);
gmsh::model::getSecondDerivative(1, 5, tSamples, deriv2);
// 计算第一个采样点的 Frenet 曲率
double dx = deriv1[0], dy = deriv1[1], dz = deriv1[2];
double ds = std::sqrt(dx*dx + dy*dy + dz*dz);
double ddx = deriv2[0], ddy = deriv2[1], ddz = deriv2[2];
double cx = dy*ddz - dz*ddy;
double cy = dz*ddx - dx*ddz;
double cz = dx*ddy - dy*ddx;
double kappa = std::sqrt(cx*cx + cy*cy + cz*cz) / (ds * ds * ds);
gmsh::logger::write("Curve 5 curvature at first sample: " +
std::to_string(kappa));
// ===== 6. 曲线在曲面上的重参数化与一致性验证 =====
std::vector<double> uvOnSurface, xyz2;
gmsh::model::reparametrizeOnSurface(1, 5, tSamples, 1, uvOnSurface);
gmsh::model::getValue(2, 1, uvOnSurface, xyz2);
double maxDiff = 0.0;
for(std::size_t i = 0; i < xyz1.size(); i++)
maxDiff = std::max(maxDiff, std::abs(xyz1[i] - xyz2[i]));
if(maxDiff < 1e-12)
gmsh::logger::write("Curve & surface evaluation match! (max diff < 1e-12)");
else
gmsh::logger::write("Mismatch: max diff = " + std::to_string(maxDiff),
"warning");
// ===== 7. 物理坐标 -> 参数坐标的反向映射 =====
std::vector<std::size_t> nTags;
std::vector<double> nCoord, nParam;
gmsh::model::mesh::getNodes(nTags, nCoord, nParam, 2, 1, true);
if(nCoord.size() >= 3) {
std::vector<double> singleCoord = {nCoord[0], nCoord[1], nCoord[2]};
std::vector<double> fromCoord;
gmsh::model::getParametrization(2, 1, singleCoord, fromCoord);
gmsh::logger::write(
"Node " + std::to_string(nTags[0]) +
" -> (u=" + std::to_string(fromCoord[0]) +
", v=" + std::to_string(fromCoord[1]) + ")");
}
// ===== 8. 启动 GUI =====
std::set<std::string> args(argv, argv + argc);
if(!args.count("-nopopup")) gmsh::fltk::run();
gmsh::finalize();
return 0;
}
25.5 关键 API 速查表
25.5.1 曲面求值(Surface Evaluation)
| API | 签名 | 说明 |
|---|---|---|
getParametrization |
(dim, tag, coord, paramCoord) |
物理坐标 -> 参数坐标。dim=1 返回 [t],dim=2 返回 [u,v] |
getParametrizationBounds |
(dim, tag, min, max) |
获取参数域范围。dim=1: min/max 各 1 个 t 值;dim=2: 各 2 个值 [u,v] |
getValue |
(dim, tag, paramCoord, coord) |
参数坐标 -> 物理坐标。dim=1: paramCoord=[t1,t2,...];dim=2: paramCoord=[u1,v1,u2,v2,...] |
getNormal |
(tag, paramCoord, normals) |
获取曲面法向量。tag 仅为面标签(整数),不使用 (dim,tag) 对 |
getCurvature |
(dim, tag, paramCoord, curvatures) |
获取最大曲率(单值/点)。使用 (dim,tag) 对 |
getPrincipalCurvatures |
(tag, paramCoord, curvMax, curvMin, dirMax, dirMin) |
获取两个主曲率及其 3D 方向向量。tag 仅为面标签 |
reparametrizeOnSurface |
(dim, tag, paramCoord, surfaceTag, surfaceParamCoord) |
将边界实体(曲线/点)的参数坐标转换到目标曲面的参数坐标 |
25.5.2 曲线求值(Curve Evaluation)
| API | 签名 | 说明 |
|---|---|---|
getValue |
(1, tag, t, xyz) |
曲线在参数 t 处的三维坐标。t 向量: [t1,t2,...],xyz: [x1,y1,z1,...] |
getDerivative |
(1, tag, t, deriv) |
一阶参数导数(切向量方向)。deriv: [dx/dt, dy/dt, dz/dt, ...] |
getSecondDerivative |
(1, tag, t, deriv2) |
二阶参数导数。deriv2: [d2x/dt2, d2y/dt2, d2z/dt2, ...] |
25.5.3 曲面求值(Surface Evaluation)— dim=2 时的导数
| API | 输出布局(每点) | 说明 |
|---|---|---|
getDerivative |
[dx/du, dy/du, dz/du, dx/dv, dy/dv, dz/dv] |
6 个分量/点:对 u 和对 v 的偏导数 |
getSecondDerivative |
[d2x/du2, d2y/du2, d2z/du2, d2x/dv2, d2y/dv2, d2z/dv2, d2x/dudv, d2y/dudv, d2z/dudv] |
9 个分量/点:二阶纯导数和混合偏导数 |
25.5.4 API 签名风格对比(重要)
| API | 实体标识方式 | 示例 |
|---|---|---|
getNormal |
仅 tag(面标签) |
getNormal(5, param, norm) |
getPrincipalCurvatures |
仅 tag(面标签) |
getPrincipalCurvatures(5, param, ...) |
getCurvature |
(dim, tag) 对 |
getCurvature(2, 5, param, curv) |
getValue |
(dim, tag) 对 |
getValue(2, 5, paramCoord, xyz) |
getDerivative |
(dim, tag) 对 |
getDerivative(1, 5, t, deriv) |
getSecondDerivative |
(dim, tag) 对 |
getSecondDerivative(1, 5, t, deriv2) |
getParametrization |
(dim, tag) 对 |
getParametrization(2, 5, coord, param) |
getParametrizationBounds |
(dim, tag) 对 |
getParametrizationBounds(1, 5, min, max) |
25.6 注意事项与最佳实践
-
getNormalvsgetCurvature的签名不一致:getNormal(tag, ...)的第一个参数是面标签(int),而getCurvature(dim, tag, ...)的第一个参数是维度(dim)。这是 Gmsh API 的历史遗留问题,混用时极易出错。建议在代码中以注释标注(如本节示例),或封装一层统一接口。 -
边界节点的参数坐标:
mesh::getNodes(..., includeBoundary=true)时,边界节点会被重新参数化(reparametrized)到当前面上。相邻两个面对同一边界节点的参数化结果通常不同(因为参数域不同),这是正常现象。 -
getValue 的 dim=0 特殊情况:对于点实体(dim=0),
getValue(0, tag, {}, coord)的parametricCoord参数为空向量{},因为点没有参数坐标。 -
getNormal 返回的是 CAD 精确法向:它不是从网格三角面片插值得到的近似法向,而是直接从 CAD B-rep 几何求值。因此即使在非常粗糙的网格上,法向量也是精确的。在压力载荷施加时应优先使用
getNormal()而非从网格计算法向。 -
主曲率与主方向的正交性:
getPrincipalCurvatures返回的两个主方向在连续几何上严格正交。但在退化曲面(如球面极点)附近,方向可能不稳定——这是微分几何的固有特性(脐点处所有方向都是主方向)。 -
getDerivative 返回的是参数导数,非弧长导数:切向量
C'(t)的模长通常不等于 1。需要单位切向量时,应除以|C'(t)|。同理,Frenet 曲率需要额外计算|C' x C''| / |C'|^3。 -
reparametrizeOnSurface 的可用性:该功能仅对部分 CAD 内核可用(特别是 OpenCASCADE)。使用前应检查返回值是否为空。对于不可用的实体,备选方案是通过投影或最近点搜索近似替代。
-
物理坐标反向映射的收敛性:
getParametrization()通过 Newton 迭代求解非线性反问题。如果提供的物理坐标离曲面太远(超出 CAD 容差),迭代可能失败或返回无意义值。建议在调用前确保坐标确实在面上(可通过isInside()预检)。 -
网格节点的参数坐标自动存储:当通过
mesh::generate()生成网格后,每个网格节点自动存储了其在所属几何实体上的参数坐标。这些坐标可通过mesh::getNodes()获取,无需再次调用getParametrization()(除非节点被移动或修改)。 -
后处理视图的性能:当网格节点数很大(如数百万)时,将所有法向量打包为单个 list-based view 可能耗尽内存。可考虑分批创建视图、使用 model-based view(依赖网格插值),或仅采样显示(每隔 N 个点取一个)。
25.7 本章小结
- 曲面参数化
(u,v) <-> (x,y,z)是 CAD 内核的核心功能,Gmsh 通过getParametrization()(物理->参数)和getValue()(参数->物理)实现双向映射,getParametrizationBounds()提供定义域边界。 getNormal(tag, ...)以面标签(int)为第一参数,返回 CAD 精确单位法向量,与getCurvature(dim, tag, ...)的(dim,tag)签名不一致,是常见错误来源。getPrincipalCurvatures()在一次调用中返回两个主曲率值及其对应的 3D 主方向向量,是自适应网格细化最重要的几何判据来源。- 曲线求值 API 族(
getValue/getDerivative/getSecondDerivative)分别提供位置、切向量和二阶导,可用于 Frenet 曲率计算和局部坐标系建立。 reparametrizeOnSurface()实现曲线在曲面上的重参数化,多用于验证边界一致性或进行边界约束的坐标转换。- 所有几何求值 API 都是直接从 CAD B-rep 求值,精度独立于网格密度——这是 Gmsh 作为几何感知(geometry-aware)前处理工具的核心优势。