Dust3D项目实训九 | 基于skeletondocument的骨骼生成分析2

本文详细解析了Dust3D软件中骨架(skeleton)的生成过程,包括节点创建、骨骼建立以及树状结构的构建。通过添加节点、连接节点形成骨骼,并对节点和骨骼进行操作,最终形成层次化的树状结构存储骨架。关键代码展示了节点和骨骼的创建逻辑,以及如何将节点与其邻居节点连接成骨骼并加入组。

2021SC@SDUSC

目录

分析概括

模块功能 

树状结构

关键代码分析

节点的创建

骨骼的创建

将节点和邻居节点形成骨骼加入组


分析概括

    在上一周进行了骨架存储结构分析以及相关类的功能分析,接上周内容,本周进行骨骼的生成以及针对骨架操作功能的分析

模块功能 

在Dust3D中,骨架的生成流程如下:

  1. 添加节点并存储节点信息
  2. 连接节点生成骨骼,存储骨骼信息
  3. 连接骨骼生成几何体各部件,存储部件信息
  4. 对节点、骨骼、部件进行移动、旋转等操作
  5. 形成树状结构存储骨架

skeletondocument.cpp的作用即为实现以上功能

树状结构

在骨架生成过程中,最重要的就是树状结构层次化的应用。树状结构如下图所示:

关键代码分析

节点的创建

节点的创建分为几种情况:

  1. 新建点id为空或不在在原骨骼中(原骨骼中无该点)
  2. 新建点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);
        }
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值