前一章讲到,为了组织计算facegroup的任务数据,调用了computeCharts函数,这个函数中将每一个mesh放到一个任务当中,并使用多线程分割mesh,并将分割后的mesh形成一个一个的facegroup,下面看看是如何分割的:
// 运行计算ComputeCharts的任务函数,计算每一个mesh分成多少个Charts的任务
static void runMeshComputeChartsTask(void *groupUserData, void *taskUserData)
{
// 组数据
auto groupArgs = (MeshComputeChartsTaskGroupArgs *)groupUserData;
// 任务数据
auto args = (MeshComputeChartsTaskArgs *)taskUserData;
// 取消就返回
if (groupArgs->progress->cancel)
return;
XA_PROFILE_START(computeChartsThread)
// Create face groups.
XA_PROFILE_START(createFaceGroups)
// 依据mesh数据创建mesh-face-group
MeshFaceGroups *meshFaceGroups = XA_NEW_ARGS(MemTag::Mesh, MeshFaceGroups, args->sourceMesh);
// 找到有拓扑关系的面,并顺序组织自来【有连接关系的面、相同的材质、不被忽略】
meshFaceGroups->compute();
// 一共分了多少个组数(每一组中是有拓扑关系的面)
const uint32_t chartGroupCount = meshFaceGroups->groupCount();
XA_PROFILE_END(createFaceGroups)
if (groupArgs->progress->cancel)
goto cleanup;
#if XA_DEBUG_EXPORT_OBJ_FACE_GROUPS
{
static std::mutex s_mutex;
std::lock_guard<std::mutex> lock(s_mutex);
char filename[256];
XA_SPRINTF(filename, sizeof(filename), "debug_face_groups.obj");
FILE *file;
XA_FOPEN(file, filename, s_faceGroupsCurrentVertex == 0 ? "w" : "a");
if (file) {
const Mesh *mesh = args->sourceMesh;
mesh->writeObjVertices(file);
// groups
uint32_t numGroups = 0;
for (uint32_t i = 0; i < mesh->faceCount(); i++) {
if (meshFaceGroups->groupAt(i) != MeshFaceGroups::kInvalid)
numGroups = max(numGroups, meshFaceGroups->groupAt(i) + 1);
}
for (uint32_t i = 0; i < numGroups; i++) {
fprintf(file, "o mesh_%03u_group_%04d\n", mesh->id(), i);
fprintf(file, "s off\n");
for (uint32_t f = 0; f < mesh->faceCount(); f++) {
if (meshFaceGroups->groupAt(f) == i)
mesh->writeObjFace(file, f, s_faceGroupsCurrentVertex);
}
}
fprintf(file, "o mesh_%03u_group_ignored\n", mesh->id());
fprintf(file, "s off\n");
for (uint32_t f = 0; f < mesh->faceCount(); f++) {
if (meshFaceGroups->groupAt(f) == MeshFaceGroups::kInvalid)
mesh->writeObjFace(file, f, s_faceGroupsCurrentVertex);
}
mesh->writeObjBoundaryEges(file);
s_faceGroupsCurrentVertex += mesh->vertexCount();
fclose(file);
}
}
#endif
// Create a chart group for each face group.
// 每一个面分组创建一个chart group
args->chartGroups->resize(chartGroupCount);
for (uint32_t i = 0; i < chartGroupCount; i++) {
// 每一个面分组都会放到chartGroups中
(*args->chartGroups)[i] = XA_NEW_ARGS(MemTag::Default, ChartGroup, i, args->sourceMesh, meshFaceGroups, MeshFaceGroups::Handle(i));
}
// Extract invalid geometry via the invalid face group (MeshFaceGroups::kInvalid).
// 通过“无效面”组提取无效几何体
{
XA_PROFILE_START(extractInvalidMeshGeometry)
args->invalidMeshGeometry->extract(args->sourceMesh, meshFaceGroups);
XA_PROFILE_END(extractInvalidMeshGeometry)
}
// One task for each chart group - compute charts. 计算charts
{
XA_PROFILE_START(chartGroupComputeChartsReal)
// Sort chart groups by face count. 按照当前mesh分成多个拓扑面组,每一个组中有很多三角面,哪一个面数多就排在最前面
Array<float> chartGroupSortData;
chartGroupSortData.resize(chartGroupCount);
for (uint32_t i = 0; i < chartGroupCount; i++)
chartGroupSortData[i] = (float)(*args->chartGroups)[i]->faceCount();
RadixSort chartGroupSort;
chartGroupSort.sort(chartGroupSortData);
// Larger chart groups are added first to reduce the chance of thread starvation.
// 首先添加较大的图表组,以减少线程不足的可能性。
ChartGroupComputeChartsTaskGroupArgs taskGroupArgs;
taskGroupArgs.atlas = groupArgs->atlas; // ctx->atlas
taskGroupArgs.options = groupArgs->options; // 输入的选项
taskGroupArgs.progress = groupArgs->progress; // 输出的进度
taskGroupArgs.taskScheduler = groupArgs->taskScheduler; // 调度
taskGroupArgs.boundaryGrid = groupArgs->boundaryGrid; // 边界的网格
taskGroupArgs.chartBuffers = groupArgs->chartBuffers; // chart的buffer
taskGroupArgs.piecewiseParam = groupArgs->piecewiseParam; //分段参数??
// 获取一个任务组
TaskGroupHandle taskGroup = groupArgs->taskScheduler->createTaskGroup(&taskGroupArgs, chartGroupCount);
for (uint32_t i = 0; i < chartGroupCount; i++) {
// 填充任务数据
Task task;
task.userData = (*args->chartGroups)[chartGroupCount - i - 1]; // 用户数据【按照有拓扑结构的面分组】
task.func = runChartGroupComputeChartsTask;
groupArgs->taskScheduler->run(taskGroup, task); // 将任务添加到任务队列中,在taskGroup所在的分组中
}
// 开启任务线程
groupArgs->taskScheduler->wait(&taskGroup);
XA_PROFILE_END(chartGroupComputeChartsReal)
}
XA_PROFILE_END(computeChartsThread)
cleanup:
// 处理完成清空
if (meshFaceGroups) {
meshFaceGroups->~MeshFaceGroups();
XA_FREE(meshFaceGroups);
}
}
其中meshFaceGroups->compute();中就是计算facegroup的过程,这个过程当中循环查找共线的面,如果一个面与相邻的面使用了相同的材质就可以将它们组织起来:
// 找到有拓扑关系的面,并顺序组织自来【有连接关系的面、相同的材质、不被忽略】
void compute()
{
// 初始化面组数组
m_groups.resize(m_mesh->faceCount());
m_groups.fillBytes(0xff); // Set all faces to kInvalid 设置所有的面无效
uint32_t firstUnassignedFace = 0;
Handle group = 0;
Array<uint32_t> growFaces;
// 面数量
const uint32_t n = m_mesh->faceCount();
m_nextFace.resize(n);
for (;;) {
// Find an unassigned face. 找一张未指定的面。
uint32_t face = UINT32_MAX;
for (uint32_t f = firstUnassignedFace; f < n; f++) {
// 这个面还未被处理,并且这个面不能被忽略
if (m_groups[f] == kInvalid && !m_mesh->isFaceIgnored(f)) {
face = f; // 面索引
firstUnassignedFace = f + 1; // 下一次从第二个面开始
break; // 没有处理的面,下面马上要处理
}
}
if (face == UINT32_MAX)
break; // All faces assigned to a group (except ignored faces). 指定给组的所有面
// 当前面所在的组face---group,很多关联的面会在同一组
m_groups[face] = group;
m_nextFace[face] = UINT32_MAX; // 下一个面??
m_firstFace.push_back(face); // 将这个面放到队列中【对所有有关联的面进行分组,firstface中存储每一个分组的第一个面索引】
growFaces.clear();
growFaces.push_back(face); // growFaces 是一个压栈,出栈的过程,每一次处理一个面的拓扑关系,处理完成后就弹出,参考3dtiles创建瓦片的过程,也是入栈、出栈
uint32_t prevFace = face, groupFaceCount = 1; // 【prevFace本次处理时的第一个面,与m_firstFace中元素对应】,【每一组有拓扑关系的面组中成员的数量groupFaceCount】
// Find faces connected to the face and assign them to the same group as the face, unless they are already assigned to another group.
// 查找连接到该面的面,并将其指定给与该面相同的组,除非已将其指定给其他组。
for (;;) {
if (growFaces.isEmpty())
break;
// 出栈
const uint32_t f = growFaces.back();
growFaces.pop_back();
// 查找面使用的材质
const uint32_t material = m_mesh->faceMaterial(f);
// 遍历每一个面的所有边【只能是两个面共享一个边,或者单独的一个边】【遍历一个三角型的三个边,与这三个边面的三个三角形连接起来】
for (Mesh::FaceEdgeIterator edgeIt(m_mesh, f); !edgeIt.isDone(); edgeIt.advance()) {
// 1、0的边与0、1的边反方向
const uint32_t oppositeEdge = m_mesh->findEdge(edgeIt.vertex1(), edgeIt.vertex0());
if (oppositeEdge == UINT32_MAX) // 没有共边的边
continue; // Boundary edge. 这是边界边缘
// 这个边所对的面
const uint32_t oppositeFace = meshEdgeFace(oppositeEdge);
if (m_mesh->isFaceIgnored(oppositeFace))
continue; // Don't add ignored faces to group. 这个面是否被忽略了
if (m_mesh->faceMaterial(oppositeFace) != material)// 共边的面使用的材质不相同
continue; // Different material. 材质不相同
if (m_groups[oppositeFace] != kInvalid) // 这个面经被占用
continue; // Connected face is already assigned to another group. 已将连接的面指定给另一个组。
m_groups[oppositeFace] = group; // 两个面有相同的边,材质也相同,就将这两个面放在同一组中,group相同
m_nextFace[oppositeFace] = UINT32_MAX; // 将这个面置为无效,
if (prevFace != UINT32_MAX)
m_nextFace[prevFace] = oppositeFace; // 前一个面指向这一个面,指向下一个面
prevFace = oppositeFace; // 这一个面
groupFaceCount++;
growFaces.push_back(oppositeFace); // 处理这一个
}
}
// 每一相同材质并且三角面有拓扑际关系的面数是多少,并且最终分了多少组
m_faceCount.push_back(groupFaceCount);
group++; // 组数
XA_ASSERT(group < kInvalid);
}
}
可以看到面1~11面是一个组,12~15是一个组。组织完成拓扑面之后,后面又启用了子线程处理面分组后是否可以创建一个chart,以及如何创建一个chart,这个任务的处理过程在runChartGroupComputeChartsTask函数当中。