上一章说到,facegroup有了,facegroup组成了chartgroup,那么如何使用chartgroup计算相应的chart呢,答案在runChartGroupComputeChartsTask中,如下:
// 使用ChartGroup计算Charts
static void runChartGroupComputeChartsTask(void *groupUserData, void *taskUserData)
{
auto args = (ChartGroupComputeChartsTaskGroupArgs *)groupUserData; // 任务所在组中的用户数据
auto chartGroup = (ChartGroup *)taskUserData; // 单个任务当中的用户数据,每一个任务一个chartgroup
if (args->progress->cancel)
return;
XA_PROFILE_START(chartGroupComputeChartsThread)
// 组中计算制图【调度、输入选项、进度、所有的chartgroup最终会汇聚到atlas中、边界网格、buffer?、??】
chartGroup->computeCharts(args->taskScheduler, *args->options, args->progress, args->atlas->get(), args->boundaryGrid, args->chartBuffers, args->piecewiseParam);
XA_PROFILE_END(chartGroupComputeChartsThread)
}
chartGroup->computeCharts中的处理过程,分段讲解:
// 使用chartgroup计算charts 【调度、输入选项、进度、所有的chartgroup最终会汇聚到atlas中、边界网格、buffer?、??】
void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, Progress *progress, segment::Atlas &atlas, ThreadLocal<UniformGrid2> *boundaryGrid, ThreadLocal<ChartCtorBuffers> *chartBuffers, ThreadLocal<PiecewiseParam> *piecewiseParam)
{
// This function may be called multiple times, so destroy existing charts.
// 函数可能被多次调用
for (uint32_t i = 0; i < m_charts.size(); i++) {
m_charts[i]->~Chart(); // 销毁chart
XA_FREE(m_charts[i]);
}
// Create mesh from source mesh, using only the faces in this face group.
// 从源网格创建网格,仅使用此面组中的面。
XA_PROFILE_START(createChartGroupMesh)
// 重新将原来的数据组织成了一个chartgroup内部mesh
Mesh *mesh = createMesh();
XA_PROFILE_END(createChartGroupMesh)
。。。。。。
}
可以看到chargroup内部又重新创建了mesh,这个创建的过程是将顶点信息、面索引等重新复制了一遍:
Mesh *createMesh()
{
XA_DEBUG_ASSERT(m_faceGroup != MeshFaceGroups::kInvalid);
// Create new mesh from the source mesh, using faces that belong to this group.
// 使用属于该组的面,从源网格创建新网格。这一组有多少个面【m_faceGroup代表第几组】
m_faceToSourceFaceMap.reserve(m_sourceMeshFaceGroups->faceCount(m_faceGroup));
// 遍历所有连接的面【有拓扑关系的面】
for (MeshFaceGroups::Iterator it(m_sourceMeshFaceGroups, m_faceGroup); !it.isDone(); it.advance())
m_faceToSourceFaceMap.push_back(it.face()); // 将面索引添加到数组中
// Only initial meshes has ignored faces. The only flag we care about is HasNormals. 只有初始网格忽略了面。我们唯一关心的标志是有正常值。
const uint32_t faceCount = m_faceToSourceFaceMap.size();
XA_DEBUG_ASSERT(faceCount > 0);
// 有些面忽略了??不知道
const uint32_t approxVertexCount = min(faceCount * 3, m_sourceMesh->vertexCount()); // 顶点数量
// 创建mesh【精度、近似顶点数、面数、是否有法线】
Mesh *mesh = XA_NEW_ARGS(MemTag::Mesh, Mesh, m_sourceMesh->epsilon(), approxVertexCount, faceCount, m_sourceMesh->flags() & MeshFlags::HasNormals);
// 顶点???
HashMap<uint32_t, PassthroughHash<uint32_t>> sourceVertexToVertexMap(MemTag::Mesh, approxVertexCount);
for (uint32_t f = 0; f < faceCount; f++) {
// 获取一个面索引
const uint32_t face = m_faceToSourceFaceMap[f];
for (uint32_t i = 0; i < 3; i++) {
// 找到顶点所在的索引
const uint32_t vertex = m_sourceMesh->vertexAt(face * 3 + i);
// 不存在就添加
if (sourceVertexToVertexMap.get(vertex) == UINT32_MAX) {
// 添加一个顶点
sourceVertexToVertexMap.add(vertex);
Vector3 normal(0.0f);
if (m_sourceMesh->flags() & MeshFlags::HasNormals)
normal = m_sourceMesh->normal(vertex); // 顶点对应的法线
// 将顶点、法线、纹理坐标添加到mesh中
mesh->addVertex(m_sourceMesh->position(vertex), normal, m_sourceMesh->texcoord(vertex));
}
}
}
// Add faces.
for (uint32_t f = 0; f < faceCount; f++) {
// 获取一个面
const uint32_t face = m_faceToSourceFaceMap[f];
XA_DEBUG_ASSERT(!m_sourceMesh->isFaceIgnored(face));
uint32_t indices[3];
for (uint32_t i = 0; i < 3; i++) {
// 获取面的顶点索引
const uint32_t vertex = m_sourceMesh->vertexAt(face * 3 + i);
indices[i] = sourceVertexToVertexMap.get(vertex);
XA_DEBUG_ASSERT(indices[i] != UINT32_MAX);
}
// Don't copy flags - ignored faces aren't used by chart groups, they are handled by InvalidMeshGeometry.
// 添加面索引 不复制标志-被忽略的面不被图表组使用,它们由InvalidMeshGeometry处理。
mesh->addFace(indices);
}
XA_PROFILE_START(createChartGroupMeshColocals)
// 串联相同的顶点
mesh->createColocals();
XA_PROFILE_END(createChartGroupMeshColocals)
XA_PROFILE_START(createChartGroupMeshBoundaries)
// 创建chart的边界
mesh->createBoundaries();
mesh->destroyEdgeMap(); // Only needed it for createBoundaries. 只是为了创建边界使用
XA_PROFILE_END(createChartGroupMeshBoundaries)
#if XA_DEBUG_EXPORT_OBJ_CHART_GROUPS
char filename[256];
XA_SPRINTF(filename, sizeof(filename), "debug_mesh_%03u_chartgroup_%03u.obj", m_sourceMesh->id(), m_id);
mesh->writeObjFile(filename);
#endif
return mesh;
}
这其中又通过mesh->createBoundaries();找出了哪些边是chartgroup(facegroup)的边,哪些不是边:
// 创建边界
void createBoundaries()
{
// 边界数、顶点数量
const uint32_t edgeCount = m_indices.size();
const uint32_t vertexCount = m_positions.size();
// 反向的边
m_oppositeEdges.resize(edgeCount);
// 边界框???
m_boundaryEdges.reserve(uint32_t(edgeCount * 0.1f));
// 边界的顶点
m_isBoundaryVertex.resize(vertexCount);
m_isBoundaryVertex.zeroOutMemory();
for (uint32_t i = 0; i < edgeCount; i++)
m_oppositeEdges[i] = UINT32_MAX;
const uint32_t faceCount = m_indices.size() / 3;
for (uint32_t i = 0; i < faceCount; i++) {
if (isFaceIgnored(i)) // 是否为忽略的面
continue;
for (uint32_t j = 0; j < 3; j++) {
const uint32_t edge = i * 3 + j;
const uint32_t vertex0 = m_indices[edge]; // 第一个点
const uint32_t vertex1 = m_indices[i * 3 + (j + 1) % 3]; // 后一个点
// If there is an edge with opposite winding to this one, the edge isn't on a boundary.
// 如果有一条边的弯曲方向与此边相反,则该边不在边界上。
const uint32_t oppositeEdge = findEdge(vertex1, vertex0);
if (oppositeEdge != UINT32_MAX) {
m_oppositeEdges[edge] = oppositeEdge; // 相反的边
} else {
m_boundaryEdges.push_back(edge); // 边
m_isBoundaryVertex.set(vertex0); // 边界上的顶点
m_isBoundaryVertex.set(vertex1); // 边界上的顶点
}
}
}
}
下面会计算mesh中的一些信息,具体计算过程在atlas.reset(mesh, options)和atlas.compute();中
// 使用chartgroup计算charts 【调度、输入选项、进度、所有的chartgroup最终会汇聚到atlas中、边界网格、buffer?、??】
void computeCharts(TaskScheduler *taskScheduler, const ChartOptions &options, Progress *progress, segment::Atlas &atlas, ThreadLocal<UniformGrid2> *boundaryGrid, ThreadLocal<ChartCtorBuffers> *chartBuffers, ThreadLocal<PiecewiseParam> *piecewiseParam)
{
。。。。。。
XA_PROFILE_START(buildAtlas)
// 将mesh和ui选项加入到图集当中
atlas.reset(mesh, options);
atlas.compute();
XA_PROFILE_END(buildAtlas)
// Update progress.
progress->increment(faceCount());
。。。。。。
}
接着上面atlas.reset(mesh, options)中会计算mesh中每一个面的面积、连接面之间的夹角、边的长度,如下所示:
void reset(const Mesh *mesh, const ChartOptions &options)
{
XA_PROFILE_START(buildAtlasInit)
m_data.options = options;
m_data.mesh = mesh;
// 计算三角面积、两个面的夹角
m_data.compute();
XA_PROFILE_END(buildAtlasInit)
}
// 计算邻接面的夹角、边的长度、面的面积
void compute()
{
// 获取面、边数量
const uint32_t faceCount = mesh->faceCount();
const uint32_t edgeCount = mesh->edgeCount();
// 面与面之间的角度
edgeDihedralAngles.resize(edgeCount);
// 边长
edgeLengths.resize(edgeCount);
// 面的面积
faceAreas.resize(faceCount);
// 是否使用uv
if (options.useInputMeshUvs)
faceUvAreas.resize(faceCount);
// 面法线
faceNormals.resize(faceCount);
// 面是否已经在chart中了
isFaceInChart.resize(faceCount);
isFaceInChart.zeroOutMemory();
// 遍历面
for (uint32_t f = 0; f < faceCount; f++) {
for (uint32_t i = 0; i < 3; i++) {
const uint32_t edge = f * 3 + i;
const Vector3 &p0 = mesh->position(mesh->vertexAt(meshEdgeIndex0(edge)));
const Vector3 &p1 = mesh->position(mesh->vertexAt(meshEdgeIndex1(edge)));
// 计算边的长度
edgeLengths[edge] = length(p1 - p0);
XA_DEBUG_ASSERT(edgeLengths[edge] > 0.0f);
}
// 计算面的面积
faceAreas[f] = mesh->computeFaceArea(f);
XA_DEBUG_ASSERT(faceAreas[f] > 0.0f);
// 使用用户uv时,计算uv面积
if (options.useInputMeshUvs)
faceUvAreas[f] = mesh->computeFaceParametricArea(f);
// 计算面法线
faceNormals[f] = mesh->computeFaceNormal(f);
}
for (uint32_t face = 0; face < faceCount; face++) {
for (uint32_t i = 0; i < 3; i++) {
const uint32_t edge = face * 3 + i; // 边
// 找到共线的面
const uint32_t oedge = mesh->oppositeEdge(edge);
if (oedge == UINT32_MAX)
edgeDihedralAngles[edge] = FLT_MAX;
else {
const uint32_t oface = meshEdgeFace(oedge);
// 通过法线计算两个面的夹角
edgeDihedralAngles[edge] = edgeDihedralAngles[oedge] = dot(faceNormals[face], faceNormals[oface]);
}
}
}
}
而atlas.compute();中的计算过程放到下一章。
|