2021SC@SDUSC
目录
分析概括
在上一周进行了骨架存储结构分析以及相关类的功能分析,接上周内容,本周进行骨骼的生成以及针对骨架操作功能的分析
![]()
模块功能
在Dust3D中,骨架的生成流程如下:
- 添加节点并存储节点信息
- 连接节点生成骨骼,存储骨骼信息
- 连接骨骼生成几何体各部件,存储部件信息
- 对节点、骨骼、部件进行移动、旋转等操作
- 形成树状结构存储骨架
skeletondocument.cpp的作用即为实现以上功能
树状结构
在骨架生成过程中,最重要的就是树状结构层次化的应用。树状结构如下图所示:

关键代码分析
节点的创建
节点的创建分为几种情况:
- 新建点id为空或不在在原骨骼中(原骨骼中无该点)
- 新建点id在原骨骼中
QUuid SkeletonDocument::createNode(QUuid nodeId, float x, float y, float z, float radius, QUuid fromNodeId)
{
QUuid partId;//设置节点所在骨骼id
const SkeletonNode *fromNode = nullptr;
bool newPartAdded = false;
if (fromNodeId.isNull()) {
//如果新建点id为空,在骨架中直接新建节点
const auto newUuid = QUuid::createUuid();
SkeletonPart &part = partMap[newUuid];
part.id = newUuid;
partId = part.id;
emit partAdded(partId);
newPartAdded = true;
} else {
//如果新建点id非空
fromNode = findNode(fromNodeId);
if (nullptr == fromNode) {
qDebug() << "Find node failed:" << fromNodeId;
return QUuid();
}
partId = fromNode->partId;
if (isPartReadonly(partId))
//若partId只读
return QUuid();
auto part = partMap.find(partId);
if (part != partMap.end())
part->second.dirty = true;
}
SkeletonNode node(nodeId);
node.partId = partId;
//设置节点圆的半径和坐标
node.setRadius(radius);
node.setX(x);
node.setY(y);
node.setZ(z);
nodeMap[node.id] = node;
partMap[partId].nodeIds.push_back(node.id);
emit nodeAdded(node.id);
if (nullptr != fromNode) {
//若存在可连接节点,将点连接形成边,为其设置上级结构
SkeletonEdge edge;
edge.partId = partId;
edge.nodeIds.push_back(fromNode->id);
edge.nodeIds.push_back(node.id);
edgeMap[edge.id] = edge;
nodeMap[node.id].edgeIds.push_back(edge.id);
nodeMap[fromNode->id].edgeIds.push_back(edge.id);
emit edgeAdded(edge.id);
}
if (newPartAdded)
addPartToComponent(partId, m_currentCanvasComponentId);
emit skeletonChanged();
return node.id;
}
骨骼的创建
骨骼边的创建是通过节点的连接生成的,通过父节点和子节点的有向连接生成对应骨骼
void SkeletonDocument::addEdge(QUuid fromNodeId, QUuid toNodeId)
{
if (findEdgeByNodes(fromNodeId, toNodeId)) {
//如果要添加的边已经存在,直接返回
qDebug() << "Add edge but edge already existed:" << fromNodeId << toNodeId;
return;
}
const SkeletonNode *fromNode = nullptr;
const SkeletonNode *toNode = nullptr;
bool toPartRemoved = false;
fromNode = findNode(fromNodeId);//将fromNodeId赋值给创建骨骼的其中一点
if (nullptr == fromNode) {
//如果未找到fromNodeId,直接返回
qDebug() << "Find node failed:" << fromNodeId;
return;
}
if (isPartReadonly(fromNode->partId))
return;
toNode = findNode(toNodeId);//将toNodeId赋值给创建骨骼的另一点
if (nullptr == toNode) {
//如果未找到toNode,直接返回
qDebug() << "Find node failed:" << toNodeId;
return;
}
if (isPartReadonly(toNode->partId))
return;
QUuid toPartId = toNode->partId;
auto fromPart = partMap.find(fromNode->partId);
if (fromPart == partMap.end()) {
qDebug() << "Find part failed:" << fromNode->partId;
return;
}
fromPart->second.dirty = true;
if (fromNode->partId != toNode->partId) {
//若构成骨骼的两节点不一致,生成骨骼
toPartRemoved = true;
std::vector<QUuid> toGroup;
std::set<QUuid> visitMap;
joinNodeAndNeiborsToGroup(&toGroup, toNodeId, &visitMap);//将骨骼以及其临接点封装成组
for (auto nodeIdIt = toGroup.begin(); nodeIdIt != toGroup.end(); nodeIdIt++) {
auto nodeIt = nodeMap.find(*nodeIdIt);//查找组合中的节点
if (nodeIt == nodeMap.end()) {
//若循环至之后一个节点任未找到,退出
qDebug() << "Find node failed:" << *nodeIdIt;
continue;
}
nodeIt->second.partId = fromNode->partId;
fromPart->second.nodeIds.push_back(nodeIt->first);
for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) {
auto edgeIt = edgeMap.find(*edgeIdIt);
if (edgeIt == edgeMap.end()) {
qDebug() << "Find edge failed:" << *edgeIdIt;
continue;
}
edgeIt->second.partId = fromNode->partId;
}
}
}
SkeletonEdge edge;//骨架边
edge.partId = fromNode->partId;//设置边的id
edge.nodeIds.push_back(fromNode->id);
edge.nodeIds.push_back(toNodeId);
edgeMap[edge.id] = edge;
nodeMap[toNodeId].edgeIds.push_back(edge.id);
nodeMap[fromNode->id].edgeIds.push_back(edge.id);
emit edgeAdded(edge.id);//添加边
if (toPartRemoved) {
updateLinkedPart(toPartId, fromNode->partId);
removePart(toPartId);
}
emit skeletonChanged();
}
将节点和邻居节点形成骨骼加入组
void SkeletonDocument::joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId)
{
if (nodeId.isNull() || visitMap->find(nodeId) != visitMap->end())
//如果节点索引为空或查找的节点不在存储中,直接返回
return;
const SkeletonNode *node = findNode(nodeId);
if (nullptr == node) {
qDebug() << "Find node failed:" << nodeId;
return;
}
visitMap->insert(nodeId);//visitMap中插入查找到的节点
group->push_back(nodeId);//原组中移除该节点
for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) {
//查找该节点的边缘节点
if (noUseEdgeId == *edgeIt)
continue;
const SkeletonEdge *edge = findEdge(*edgeIt);
//查找对应边id
if (nullptr == edge) {
qDebug() << "Find edge failed:" << *edgeIt;
continue;
}
for (auto nodeIt = edge->nodeIds.begin(); nodeIt != edge->nodeIds.end(); nodeIt++) {
//将对应点和相邻节点组成的边信息组成组合
joinNodeAndNeiborsToGroup(group, *nodeIt, visitMap, noUseEdgeId);
}
}
}
本文详细解析了Dust3D软件中骨架(skeleton)的生成过程,包括节点创建、骨骼建立以及树状结构的构建。通过添加节点、连接节点形成骨骼,并对节点和骨骼进行操作,最终形成层次化的树状结构存储骨架。关键代码展示了节点和骨骼的创建逻辑,以及如何将节点与其邻居节点连接成骨骼并加入组。
468

被折叠的 条评论
为什么被折叠?



