Learning records of source code analysis

Posted by enkidu72 on Sat, 09 Oct 2021 01:42:57 +0200


Since this week, our team has officially launched the analysis of Dust3D source code. After group discussion, we roughly divided the files into three modules: model construction, bone and animation, rendering and material according to the file name of the open source project. Among them, we speculate that the file beginning with mesh - is related to the construction of the core function model of Dust3D, so I will start learning from the part of mesh generator this week.

MeshGenerator class

MeshGenerator is a public derived class of QObject. QObject is the core of Qt module. Its main feature is the mechanism of seamless communication between objects: signal and slot. This mechanism enables seamless interaction between Qt objects.

class MeshGenerator : public QObject
    MeshGenerator(Snapshot *snapshot);
    bool isSuccessful();
    Model *takeResultMesh();
    Model *takePartPreviewMesh(const QUuid &partId);
    QImage *takePartPreviewImage(const QUuid &partId);
    const std::set<QUuid> &generatedPreviewPartIds();
    const std::set<QUuid> &generatedPreviewImagePartIds();
    Object *takeObject();
    std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *takeCutFaceTransforms();
    std::map<QUuid, std::map<QString, QVector2D>> *takeNodesCutFaces();
    void generate();
    void setGeneratedCacheContext(GeneratedCacheContext *cacheContext);
    void setSmoothShadingThresholdAngleDegrees(float degrees);
    void setInterpolationEnabled(bool interpolationEnabled);
    void setDefaultPartColor(const QColor &color);
    void setId(quint64 id);
    void setWeldEnabled(bool enabled);
    quint64 id();
    void finished();
public slots:
    void process();
    QColor m_defaultPartColor = Qt::white;
    Snapshot *m_snapshot = nullptr;
    GeneratedCacheContext *m_cacheContext = nullptr;
    std::set<QString> m_dirtyComponentIds;
    std::set<QString> m_dirtyPartIds;
    float m_mainProfileMiddleX = 0;
    float m_sideProfileMiddleX = 0;
    float m_mainProfileMiddleY = 0;
    Object *m_object = nullptr;
    std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> m_nodeVertices;
    std::map<QString, std::set<QString>> m_partNodeIds;
    std::map<QString, std::set<QString>> m_partEdgeIds;
    std::set<QUuid> m_generatedPreviewPartIds;
    std::set<QUuid> m_generatedPreviewImagePartIds;
    Model *m_resultMesh = nullptr;
    std::map<QUuid, Model *> m_partPreviewMeshes;
    std::map<QUuid, QImage *> m_partPreviewImages;
    bool m_isSuccessful = false;
    bool m_cacheEnabled = false;
    float m_smoothShadingThresholdAngleDegrees = 60;
    std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *m_cutFaceTransforms = nullptr;
    std::map<QUuid, std::map<QString, QVector2D>> *m_nodesCutFaces = nullptr;
    quint64 m_id = 0;
    std::vector<QVector3D> m_clothCollisionVertices;
    std::vector<std::vector<size_t>> m_clothCollisionTriangles;
    bool m_weldEnabled = true;
    bool m_interpolationEnabled = true;
    void collectParts();
    void collectIncombinableComponentMeshes(const QString &componentIdString);
    void collectIncombinableMesh(const MeshCombiner::Mesh *mesh, const GeneratedComponent &componentCache);
    bool checkIsComponentDirty(const QString &componentIdString);
    bool checkIsPartDirty(const QString &partIdString);
    bool checkIsPartDependencyDirty(const QString &partIdString);
    void checkDirtyFlags();
    bool fillPartWithMesh(GeneratedPart &partCache, 
        const QUuid &fillMeshFileId,
        float deformThickness,
        float deformWidth,
        float cutRotation,
        const StrokeMeshBuilder *strokeMeshBuilder);
    MeshCombiner::Mesh *combinePartMesh(const QString &partIdString, bool *hasError, bool *retryable, bool addIntermediateNodes=true);
    MeshCombiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode);
    void makeXmirror(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceFaces,
        std::vector<QVector3D> *destVertices, std::vector<std::vector<size_t>> *destFaces);
    void collectSharedQuadEdges(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces,
        std::set<std::pair<PositionKey, PositionKey>> *sharedQuadEdges);
    MeshCombiner::Mesh *combineTwoMeshes(const MeshCombiner::Mesh &first, const MeshCombiner::Mesh &second,
        MeshCombiner::Method method,
        bool recombine=true);
    void generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
        const std::vector<QVector3D> &triangleNormals,
        std::vector<std::vector<QVector3D>> *triangleVertexNormals);
    const std::map<QString, QString> *findComponent(const QString &componentIdString);
    CombineMode componentCombineMode(const std::map<QString, QString> *component);
    MeshCombiner::Mesh *combineComponentChildGroupMesh(const std::vector<QString> &componentIdStrings,
        GeneratedComponent &componentCache);
    MeshCombiner::Mesh *combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine=true);
    QString componentColorName(const std::map<QString, QString> *component);
    void collectUncombinedComponent(const QString &componentIdString);
    void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate);
    void postprocessObject(Object *object);
    void collectErroredParts();
    void preprocessMirror();
    QString reverseUuid(const QString &uuidString);

The evolution of the mesh surface is determined by the scalar field defined by the nodal sphere. In order to allow local operations on the shape, we can add some auxiliary node spheres to affect the scalar field, so as to control the evolution of the mesh surface.
The constructor of MeshGenerator class obtains the canvas, vertex, edge, part, component and other information of the currently input snapshot, and declares a series of functions such as mesh filling, mesh combination and vertex normal smoothing for model mesh control.

The MeshGenerator class contains several functions that are important in terms of Name:

Function name/
fillPartWithMeshMesh filling
combinePartMeshComposite grid
combineComponentMeshComposite component grid
makeXmirrorCreates a mirror image along the X axis
combineTwoMeshesCombine two meshes
generateSmoothTriangleVertexNormalsGenerates smooth triangle vertex normals
combineComponentChildGroupMeshComposite component subgroup grid
combineMultipleMeshesCombine multiple meshes

fillPartWithMesh() function

Dust3D realizes seamless stitching of meshes of multiple sub models during modeling, and fillPartWithMesh() function is one of the functions required to realize this function.
The fillPartWithMesh() function returns a bool value for judgment. If the fillMesh area is empty, it returns false. If the fillMesh area exists, it strokes the input partCache, inserts the obtained object points and edges into the partCache, and finally creates patches according to all the point and edge information in the partCache to generate the mesh of the fillMesh area, and returns true, Indicates that the filling is successful.

bool MeshGenerator::fillPartWithMesh(GeneratedPart &partCache, 
    const QUuid &fillMeshFileId,
    float deformThickness,
    float deformWidth,
    float cutRotation,
    const StrokeMeshBuilder *strokeMeshBuilder)
    bool fillIsSucessful = false;
    const QByteArray *fillMeshByteArray = FileForever::getContent(fillMeshFileId);
    if (nullptr == fillMeshByteArray)
        return false;
    QXmlStreamReader fillMeshStream(*fillMeshByteArray);  
    Snapshot *fillMeshSnapshot = new Snapshot;
    loadSkeletonFromXmlStream(fillMeshSnapshot, fillMeshStream);
    GeneratedCacheContext *fillMeshCacheContext = new GeneratedCacheContext();
    MeshGenerator *meshGenerator = new MeshGenerator(fillMeshSnapshot);
    fillIsSucessful = meshGenerator->isSuccessful();
    Object *object = meshGenerator->takeObject();
    if (nullptr != object) {
        MeshStroketifier stroketifier;
        std::vector<MeshStroketifier::Node> strokeNodes;
        for (const auto &nodeIndex: strokeMeshBuilder->nodeIndices()) {
            const auto &node = strokeMeshBuilder->nodes()[nodeIndex];
            MeshStroketifier::Node strokeNode;
            strokeNode.position = node.position;
            strokeNode.radius = node.radius;
        if (stroketifier.prepare(strokeNodes, object->vertices)) {
            std::vector<MeshStroketifier::Node> agentNodes(object->nodes.size());
            for (size_t i = 0; i < object->nodes.size(); ++i) {
                auto &dest = agentNodes[i];
                const auto &src = object->nodes[i];
                dest.position = src.origin;
                dest.radius = src.radius;
            for (size_t i = 0; i < object->nodes.size(); ++i) {
                const auto &src = agentNodes[i];
                auto &dest = object->nodes[i];
                dest.origin = src.position;
                dest.radius = src.radius;
        partCache.objectNodes.insert(partCache.objectNodes.end(), object->nodes.begin(), object->nodes.end());
        partCache.objectEdges.insert(partCache.objectEdges.end(), object->edges.begin(), object->edges.end());
        partCache.vertices.insert(partCache.vertices.end(), object->vertices.begin(), object->vertices.end());
        if (!strokeNodes.empty()) {
            for (auto &it: partCache.vertices)
                it += strokeNodes.front().position;
        for (size_t i = 0; i < object->vertexSourceNodes.size(); ++i)
            partCache.objectNodeVertices.push_back({partCache.vertices[i], object->vertexSourceNodes[i]});
        partCache.faces.insert(partCache.faces.end(), object->triangleAndQuads.begin(), object->triangleAndQuads.end());
        fillIsSucessful = true;
    delete object;
    delete meshGenerator;
    delete fillMeshCacheContext;

    return fillIsSucessful;

The fillPartMesh() function will be called later in the combinePartMesh() function, which is used for the mesh filling when the strokeMeshBuilder only contains the initialization normals. Since the combinePartMesh() function has 500 + lines defined, we will discuss it later.

makeXmirror() function

An important function of Dust3D is that for paired components such as arms and legs, you can select the X-axis mirror image to automatically generate a symmetrical model. The implementation method is to take the X value of the vertex coordinates (x0,y0,z0) of the source model as the vertex coordinates of the target model, and then flip the start and end points of each patch of the source model as the patch of the target model. Generally, the storage order of patch vertices is clockwise / counterclockwise, while the patches invisible to the camera are stored in the opposite direction, so the vertex order needs to be reversed after the mirror.

void MeshGenerator::makeXmirror(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceFaces,
        std::vector<QVector3D> *destVertices, std::vector<std::vector<size_t>> *destFaces)
    for (const auto &mirrorFrom: sourceVertices) {
        destVertices->push_back(QVector3D(-mirrorFrom.x(), mirrorFrom.y(), mirrorFrom.z()));
    std::vector<std::vector<size_t>> newFaces;
    for (const auto &mirrorFrom: sourceFaces) {
        auto newFace = mirrorFrom;
        std::reverse(newFace.begin(), newFace.end());

Topics: C++ Qt 3d