Learning record of source code analysis -- bone storage and establishment

2021SC@SDUSC

Bones determine the position and orientation of the whole model in the world coordinate system. When rendering a static model, because the vertices of the model are defined in the model coordinate system, it is only necessary to transform the model coordinate system of each vertex to the world coordinate system. For bone animation, we set the position and orientation of the model, which is actually setting the position and orientation of the root bone, and then calculate the position and orientation of each bone according to the transformation information between parent and child bones in the bone hierarchy.

Bone data structure

The skeleton of any type of character, whether human, upright or reptile, can be described as a tree structure gradually expanded with a joint as the root node. The whole skeleton is a tree structure description containing parent-child relationship.

Dust3D provides a unified bone tissue form for (lactating) animals, which is divided into Neck, Limb, Tail and Joint marks. Users can choose the bone type of "Animal" when using. For non biological or other irregular bones, you can use Joint to customize the marking of Joint parts.

//Bone marker
enum class BoneMark
{
    None = 0,
    Neck,
    Limb,
    Tail,
    Joint,
    Count
};

Bone class, used to define a controllable bone.

class RigBone
{
public:
    QString name;
    int index = -1;
    int parent = -1;
    //Position of bone head
    QVector3D headPosition;  
    //Position of the bone tail
    QVector3D tailPosition;
    //Bone head node radius
    float headRadius = 0.0;
    //Bone tail node radius
    float tailRadius = 0.0;
    QColor color;
    std::map<QString, QString> attributes;
    //Vector pointing to child bones
    std::vector<int> children;
};

The joint node class stores the parent node index, child node index, current node position and transformation information.
bindMatrix: Matrix4 (usually 4x4 matrix) representing the basic transformation of bones. Bind the skeleton to a skin mesh, and its inverse matrix invertbindmatrix will also be calculated.
JointNodeTree stores the RigBone and JointNode sequences.

struct JointNode
{
    //Parent node index
    int parentIndex;
    QString name;
    //Node location
    QVector3D position;
    //Transform information
    QVector3D bindTranslation;
    QVector3D translation;
    QQuaternion rotation;
    QMatrix4x4 bindMatrix;
    QMatrix4x4 inverseBindMatrix;
    //Child node index
    std::vector<int> children;
};

class JointNodeTree
{
public:
    const std::vector<JointNode> &nodes() const;
    JointNodeTree(const std::vector<RigBone> *resultRigBones);
    void updateRotation(int index, const QQuaternion &rotation);
    void updateTranslation(int index, const QVector3D &translation);
    void updateMatrix(int index, const QMatrix4x4 &matrix);
private:
    std::vector<JointNode> m_boneNodes;
};

updateMatrix

We start from a vertex, assuming that the vertex is affected by bone2. Bone B has a parent node bone A. We know the bindmatrix matrix from bone B to its parent node bone a and the transformation matrix from bone B to the world coordinate system. This matrix will help us transform the mesh vertex in the world coordinate system into the coordinate system of bone B, And the transformation matrix from bone a to the world coordinate system, then the final position of a vertex = the transformation matrix from bone a to the world coordinate system × bindMatrix × Transformation matrix from skeleton a to world coordinate system × Original location.

void JointNodeTree::updateMatrix(int index, const QMatrix4x4 &matrix)
{
    const QMatrix4x4 &localMatrix = matrix;
    
    updateTranslation(index, 
        QVector3D(localMatrix(0, 3), localMatrix(1, 3), localMatrix(2, 3)));
    
    //Convert local coordinates to world coordinates
    float scalar = std::sqrt(std::max(0.0f, 1.0f + localMatrix(0, 0) + localMatrix(1, 1) + localMatrix(2, 2))) / 2.0f;
    float x = std::sqrt(std::max(0.0f, 1.0f + localMatrix(0, 0) - localMatrix(1, 1) - localMatrix(2, 2))) / 2.0f;
    float y = std::sqrt(std::max(0.0f, 1.0f - localMatrix(0, 0) + localMatrix(1, 1) - localMatrix(2, 2))) / 2.0f;
    float z = std::sqrt(std::max(0.0f, 1.0f - localMatrix(0, 0) - localMatrix(1, 1) + localMatrix(2, 2))) / 2.0f;
    x *= x * (localMatrix(2, 1) - localMatrix(1, 2)) > 0 ? 1 : -1;
    y *= y * (localMatrix(0, 2) - localMatrix(2, 0)) > 0 ? 1 : -1;
    z *= z * (localMatrix(1, 0) - localMatrix(0, 1)) > 0 ? 1 : -1;
    float length = std::sqrt(scalar * scalar + x * x + y * y + z * z);
    updateRotation(index, 
        QQuaternion(scalar / length, x / length, y / length, z / length));
}

Establish bone tree structure

JointNodeTree::JointNodeTree(const std::vector<RigBone> *resultRigBones)
{
    if (nullptr == resultRigBones || resultRigBones->empty())
        return;
    
    m_boneNodes.resize(resultRigBones->size());
    
    m_boneNodes[0].parentIndex = -1;
    //Link the elements in the RigBones sequence
    for (decltype(resultRigBones->size()) i = 0; i < resultRigBones->size(); i++) {
        const auto &bone = (*resultRigBones)[i];
        auto &node = m_boneNodes[i];
        node.name = bone.name;
        node.position = bone.headPosition;
        QMatrix4x4 parentMatrix;
        //When the current node is the root node, the transformation information is the node position
        if (-1 == node.parentIndex) {
            node.bindTranslation = node.position;
        } else {
            //Otherwise, the transformation information from the child node to the parent node is calculated
            const auto &parentNode = m_boneNodes[node.parentIndex];
            node.bindTranslation = node.position - parentNode.position;
            parentMatrix = parentNode.bindMatrix;
        }
        QMatrix4x4 translationMatrix;
        translationMatrix.translate(node.bindTranslation);
        node.bindMatrix = parentMatrix * translationMatrix;
        //Calculate the inverse of the transformation matrix
        node.inverseBindMatrix = node.bindMatrix.inverted();
        node.children = bone.children;
        //Sets the parent node index of the child node of the current node
        for (const auto &childIndex: bone.children)
            m_boneNodes[childIndex].parentIndex = i;
    }
}

Create a linked list of bone nodes

void RigGenerator::buildBoneNodeChain()
{
    std::vector<std::tuple<size_t, std::unordered_set<size_t>, bool>> segments;
    std::unordered_set<size_t> middle;
    size_t middleStartNodeIndex = m_object->nodes.size();
    for (size_t nodeIndex = 0; nodeIndex < m_object->nodes.size(); ++nodeIndex) {
        const auto &node = m_object->nodes[nodeIndex];
        if (!BoneMarkIsBranchNode(node.boneMark))
            //If the current node is the node of the skeleton branch, it enters the next cycle
            continue;
        m_branchNodesMapByMark[(int)node.boneMark].push_back(nodeIndex);
        if (BoneMark::Neck == node.boneMark) {
            //Node marked Neck
            if (middleStartNodeIndex == m_object->nodes.size())
                middleStartNodeIndex = nodeIndex;
        } else if (BoneMark::Tail == node.boneMark) {
            //Node marked Tail
            middleStartNodeIndex = nodeIndex;
        }
        std::unordered_set<size_t> left;
        std::unordered_set<size_t> right;
        //The child nodes are divided into two trees according to the node index
        splitByNodeIndex(nodeIndex, &left, &right);
        if (left.size() > right.size())
            //Control the height difference between the left and right, and always the height of the left subtree
            std::swap(left, right);
        for (const auto &it: right)
            middle.insert(it);
        segments.push_back(std::make_tuple(nodeIndex, left, false));
    }
    for (const auto &it: segments) {
        const auto &nodeIndex = std::get<0>(it);
        const auto &left = std::get<1>(it);
        for (const auto &it: left)
            middle.erase(it);
        middle.erase(nodeIndex);
    }
    middle.erase(middleStartNodeIndex);
    if (middleStartNodeIndex != m_object->nodes.size())
        //Establish tuples for the root node and the remaining middle sequence
        segments.push_back(std::make_tuple(middleStartNodeIndex, middle, true));
    for (const auto &it: segments) {
        const auto &fromNodeIndex = std::get<0>(it);
        const auto &left = std::get<1>(it);
        const auto &isSpine = std::get<2>(it);
        const auto &fromNode = m_object->nodes[fromNodeIndex];
        std::vector<std::vector<size_t>> boneNodeIndices;
        std::unordered_set<size_t> visited;
        size_t attachNodeIndex = fromNodeIndex;
        //Recursive collection of bone nodes
        collectNodesForBoneRecursively(fromNodeIndex,
            &left,
            &boneNodeIndices,
            0,
            &visited);
        //Find the end of the limb node
        if (BoneMark::Limb == fromNode.boneMark) {
            for (const auto &neighbor: m_neighborMap[fromNodeIndex]) {
                if (left.find(neighbor) == left.end()) {
                    attachNodeIndex = neighbor;
                    break;
                }
            }
        }
        //Store (root node, bone node, spine node, end node) tuple sequence
        m_boneNodeChain.push_back({fromNodeIndex, boneNodeIndices, isSpine, attachNodeIndex});
    }
    for (size_t i = 0; i < m_boneNodeChain.size(); ++i) {
        const auto &chain = m_boneNodeChain[i];
        const auto &node = m_object->nodes[chain.fromNodeIndex];
        const auto &isSpine = chain.isSpine;
        //Store the nodes with corresponding tags in the corresponding sequence
        if (isSpine) {
            m_spineChains.push_back(i);
            continue;
        }
        if (BoneMark::Neck == node.boneMark) {
            m_neckChains.push_back(i);
        } else if (BoneMark::Tail == node.boneMark) {
            m_tailChains.push_back(i);
        } else if (BoneMark::Limb == node.boneMark) {
            if (node.origin.x() > 0) {
                m_leftLimbChains.push_back(i);
            } else if (node.origin.x() < 0) {
                m_rightLimbChains.push_back(i);
            }
        }
    }
}

Tags: data structure

Posted on Sat, 06 Nov 2021 14:07:41 -0400 by jamz310