Search code examples
c++3dqt3d

Qt3DRender : How to render an isosurface from quadrilateral patches?


I extracted an isosurface with the dual marching cubes algorithm.

From the algorithm, I got the following data:

# cube.obj wavefront file
## shared vertices (cartesian coordinates)
v 1.0 1.0 0.0
v 0.0 1.0 0.0
v 0.0 0.0 0.0
v 1.0 0.0 0.0
v 1.0 0.0 1.0
v 0.0 0.0 1.0
v 0.0 1.0 1.0
v 1.0 1.0 1.0
## faces (vertex indices are forming quad patches)
f 1 4 3 2
f 5 6 3 4
f 7 2 3 6
f 8 5 4 1
f 8 1 2 7
f 8 7 6 5

(Plotting triangulated surfaces is explained here).

Main question: Is it possible to render an isosurface from this quadrilateral face data with Qt3d?

Bonus question: How can I make the surface transparent or plot it as a wireframe?

This is how far I got:

#include <QApplication>
#include <QWidget>
#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DExtras/QOrbitCameraController>
#include <Qt3DRender/QCamera>
#include <Qt3DCore/QEntity>
#include <Qt3DCore/QTransform>
#include <Qt3DRender/QGeometryRenderer>
#include <Qt3DRender/QAttribute>
#include <Qt3DRender/QBuffer>
#include <Qt3DExtras/QPhongAlphaMaterial>

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);

    // Root entity
    auto *rootEntity = new Qt3DCore::QEntity();

    // Window container
    auto qt3DWindow = new Qt3DExtras::Qt3DWindow();
    qt3DWindow->setRootEntity(rootEntity);
    auto widget = QWidget::createWindowContainer(qt3DWindow);

    // Camera
    auto *camController = new Qt3DExtras::QOrbitCameraController(rootEntity);

    qt3DWindow->setRootEntity(rootEntity);
    qt3DWindow->camera()->lens()->setPerspectiveProjection(45.0f, 16.0f / 9.0f, 0.1f, 100.0f);
    qt3DWindow->camera()->setPosition(QVector3D(2.5, -8, 0.0));
    qt3DWindow->camera()->setViewCenter(QVector3D(0, 0, 0));

    // For camera controls
    camController->setLinearSpeed(50.f);
    camController->setLookSpeed(180.f);
    camController->setCamera(qt3DWindow->camera());

    // Material
    auto *material = new Qt3DExtras::QPhongAlphaMaterial(rootEntity);
    material->setSpecular(Qt::white);
    material->setShininess(0);
    material->setAmbient(Qt::red);
    material->setAlpha(0.5);

    // Transform
    auto *transform = new Qt3DCore::QTransform;
    transform->setScale(1.0f);

    auto *customMeshEntity = new Qt3DCore::QEntity(rootEntity);

    // Custom Mesh
    auto *customMeshRenderer = new Qt3DRender::QGeometryRenderer;
    auto *customGeometry = new Qt3DRender::QGeometry(customMeshRenderer);

    auto *vertexDataBuffer = new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer, customGeometry);
    auto *indexDataBuffer = new Qt3DRender::QBuffer(Qt3DRender::QBuffer::IndexBuffer, customGeometry);

    // Vertices
    auto nVertices = 8;
    auto nCoordinates = 3; // cartesian coordinates
    QByteArray vertexBufferData;
    vertexBufferData.resize(nVertices * nCoordinates * sizeof(float));

    auto *rawVertexArray = reinterpret_cast<float *>(vertexBufferData.data());
    // Vertex 1
    rawVertexArray[0*nCoordinates+0] = 1.0f;
    rawVertexArray[0*nCoordinates+1] = 1.0f;
    rawVertexArray[0*nCoordinates+2] = 0.0f;
    // Vertex 2
    rawVertexArray[1*nCoordinates+0] = 0.0f;
    rawVertexArray[1*nCoordinates+1] = 1.0f;
    rawVertexArray[1*nCoordinates+2] = 0.0f;
    // Vertex 3
    rawVertexArray[2*nCoordinates+0] = 0.0f;
    rawVertexArray[2*nCoordinates+1] = 0.0f;
    rawVertexArray[2*nCoordinates+2] = 0.0f;
    // Vertex 4
    rawVertexArray[3*nCoordinates+0] = 1.0f;
    rawVertexArray[3*nCoordinates+1] = 0.0f;
    rawVertexArray[3*nCoordinates+2] = 0.0f;
    // Vertex 5
    rawVertexArray[4*nCoordinates+0] = 1.0f;
    rawVertexArray[4*nCoordinates+1] = 0.0f;
    rawVertexArray[4*nCoordinates+2] = 1.0f;
    // Vertex 6
    rawVertexArray[5*nCoordinates+0] = 0.0f;
    rawVertexArray[5*nCoordinates+1] = 0.0f;
    rawVertexArray[5*nCoordinates+2] = 1.0f;
    // Vertex 7
    rawVertexArray[6*nCoordinates+0] = 0.0f;
    rawVertexArray[6*nCoordinates+1] = 1.0f;
    rawVertexArray[6*nCoordinates+2] = 1.0f;
    // Vertex 8
    rawVertexArray[7*nCoordinates+0] = 1.0f;
    rawVertexArray[7*nCoordinates+1] = 1.0f;
    rawVertexArray[7*nCoordinates+2] = 1.0f;

    vertexDataBuffer->setData(vertexBufferData);

    // Faces
    unsigned nFaces = 6;
    unsigned nIndicesPerFace = 4;
    QByteArray indexBufferData;
    indexBufferData.resize(nFaces * nIndicesPerFace * sizeof(ushort));

    auto *rawIndexArray = reinterpret_cast<ushort *>(indexBufferData.data());
    // Face 1
    rawIndexArray[0*nIndicesPerFace+0] = 1;
    rawIndexArray[0*nIndicesPerFace+1] = 4;
    rawIndexArray[0*nIndicesPerFace+2] = 3;
    rawIndexArray[0*nIndicesPerFace+3] = 2;
    // Face 2
    rawIndexArray[1*nIndicesPerFace+0] = 5;
    rawIndexArray[1*nIndicesPerFace+1] = 6;
    rawIndexArray[1*nIndicesPerFace+2] = 3;
    rawIndexArray[1*nIndicesPerFace+3] = 4;
    // Face 3
    rawIndexArray[2*nIndicesPerFace+0] = 7;
    rawIndexArray[2*nIndicesPerFace+1] = 2;
    rawIndexArray[2*nIndicesPerFace+2] = 3;
    rawIndexArray[2*nIndicesPerFace+3] = 6;
    // Face 4
    rawIndexArray[3*nIndicesPerFace+0] = 8;
    rawIndexArray[3*nIndicesPerFace+1] = 5;
    rawIndexArray[3*nIndicesPerFace+2] = 4;
    rawIndexArray[3*nIndicesPerFace+3] = 1;
    // Face 5
    rawIndexArray[4*nIndicesPerFace+0] = 8;
    rawIndexArray[4*nIndicesPerFace+1] = 1;
    rawIndexArray[4*nIndicesPerFace+2] = 2;
    rawIndexArray[4*nIndicesPerFace+3] = 7;
    // Face 6
    rawIndexArray[5*nIndicesPerFace+0] = 8;
    rawIndexArray[5*nIndicesPerFace+1] = 7;
    rawIndexArray[5*nIndicesPerFace+2] = 6;
    rawIndexArray[5*nIndicesPerFace+3] = 5;

    indexDataBuffer->setData(indexBufferData);

    // Attributes
    auto *positionAttribute = new Qt3DRender::QAttribute();
    positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
    positionAttribute->setBuffer(vertexDataBuffer);
    positionAttribute->setDataType(Qt3DRender::QAttribute::Float);
    positionAttribute->setDataSize(3);
    positionAttribute->setByteOffset(0);
    positionAttribute->setByteStride(3 * sizeof(float));
    positionAttribute->setCount(nVertices);
    positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName());

    auto *indexAttribute = new Qt3DRender::QAttribute();
    indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute);
    indexAttribute->setBuffer(indexDataBuffer);
    indexAttribute->setDataType(Qt3DRender::QAttribute::UnsignedShort);
    indexAttribute->setDataSize(1);
    indexAttribute->setByteOffset(0);
    indexAttribute->setByteStride(0);
    indexAttribute->setCount(nFaces*nIndicesPerFace);

    customGeometry->addAttribute(positionAttribute);
    customGeometry->addAttribute(indexAttribute);

    customMeshRenderer->setInstanceCount(1);
    customMeshRenderer->setFirstVertex(0);
    customMeshRenderer->setIndexOffset(1); // first index is 1
    customMeshRenderer->setFirstInstance(0);
    customMeshRenderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Patches);
    customMeshRenderer->setVerticesPerPatch(4);
    customMeshRenderer->setGeometry(customGeometry);
    customMeshRenderer->setVertexCount(nFaces * nIndicesPerFace);

    customMeshEntity->addComponent(customMeshRenderer);
    customMeshEntity->addComponent(transform);
    customMeshEntity->addComponent(material);

    qt3DWindow->setRootEntity(rootEntity);
    widget->show();

    return QApplication::exec();
}

The rendered scene remains empty, so I assume I did something wrong here. I am grateful for any help!


Solution

  • I worked out the solution. Following the suggestion of @peppe, I plotted triangles. I also calculated the vertex normals.

    Here the code:

    #include <QApplication>
    #include <QWidget>
    #include <Qt3DExtras/Qt3DWindow>
    #include <Qt3DExtras/QOrbitCameraController>
    #include <Qt3DRender/QCamera>
    #include <Qt3DCore/QEntity>
    #include <Qt3DCore/QTransform>
    #include <Qt3DRender/QGeometryRenderer>
    #include <Qt3DRender/QAttribute>
    #include <Qt3DRender/QBuffer>
    #include <Qt3DExtras/QPhongAlphaMaterial>
    
    #include <iostream>
    #include <assert.h>
    #include <utility>
    
    struct Vertex{
    
        Vertex(QVector3D p, ushort id)
        : position(p), normal({0,0,0}), index(id){}
    
        QVector3D position, normal;
        ushort index;
    };
    
    struct Quad{
        Quad() = delete;
        explicit Quad(std::vector<Vertex> v)
                : vertices(v) {
            assert(vertices.size() == 4);
        }
    
        std::vector<Vertex> vertices;
    };
    struct Triangle{
        Triangle() = delete;
        explicit Triangle(std::vector<Vertex> v)
        : vertices(v), faceNormal(QVector3D::normal(v[0].position,v[1].position,v[2].position)) {
            assert(vertices.size() == 3);
        }
    
        std::vector<Vertex> vertices;
        QVector3D faceNormal;
    };
    
    using trianlgePair = std::pair<Triangle,Triangle>;
    
    trianlgePair quadToTriangle(Quad quad) {
        return {Triangle({quad.vertices[0], quad.vertices[1], quad.vertices[2]}),
                Triangle({quad.vertices[2], quad.vertices[3], quad.vertices[0]})};
    }
    
    int main(int argc, char* argv[])
    {
        std::vector<Vertex>vertices({
            Vertex({1.0f, 1.0f, 0.0f}, 1-1),
            Vertex({0.0f, 1.0f, 0.0f}, 2-1),
            Vertex({0.0f, 0.0f, 0.0f}, 3-1),
            Vertex({1.0f, 0.0f, 0.0f}, 4-1),
            Vertex({1.0f, 0.0f, 1.0f}, 5-1),
            Vertex({0.0f, 0.0f, 1.0f}, 6-1),
            Vertex({0.0f, 1.0f, 1.0f}, 7-1),
            Vertex({1.0f, 1.0f, 1.0f}, 8-1)
        });
    
        std::vector<Quad> quads({
            Quad({vertices[1-1], vertices[4-1], vertices[3-1], vertices[2-1]}),
            Quad({vertices[5-1], vertices[6-1], vertices[3-1], vertices[4-1]}),
            Quad({vertices[7-1], vertices[2-1], vertices[3-1], vertices[6-1]}),
            Quad({vertices[8-1], vertices[5-1], vertices[4-1], vertices[1-1]}),
            Quad({vertices[8-1], vertices[1-1], vertices[2-1], vertices[7-1]}),
            Quad({vertices[8-1], vertices[7-1], vertices[6-1], vertices[5-1]})
        });
        unsigned nCoordinates = 3; // cartesian coordinates
    
        // Triangles
        std::vector<Triangle> triangles;
        for(const auto& quad : quads){
    
            auto trianglePair = quadToTriangle(quad);
    
            triangles.push_back(trianglePair.first);
            triangles.push_back(trianglePair.second);
        }
        unsigned nIndicesPerTriangle= 3;
    
        // vertex normals
        for (auto it = vertices.begin(); it != vertices.end(); ++it) {
    
            QVector3D vertexNormal = {0,0,0};
    
            // find triangles that contain the vertex
            for(const auto& t : triangles) {
                if(std::find_if(t.vertices.begin(), t.vertices.end(),
                        [it](const Vertex& v)->bool { return v.index == it.base()->index; }
                        ) != t.vertices.end())
                    vertexNormal += t.faceNormal;
            }
    
            it.base()->normal = vertexNormal.normalized();
        }
    
    
        QApplication app(argc, argv);
    
        // Root entity
        auto *rootEntity = new Qt3DCore::QEntity();
    
        // Window container
        auto qt3DWindow = new Qt3DExtras::Qt3DWindow();
        qt3DWindow->setRootEntity(rootEntity);
        auto widget = QWidget::createWindowContainer(qt3DWindow);
    
        // Camera
        auto *camController = new Qt3DExtras::QOrbitCameraController(rootEntity);
    
        qt3DWindow->setRootEntity(rootEntity);
        qt3DWindow->camera()->lens()->setPerspectiveProjection(45.0f, 16.0f / 9.0f, 0.1f, 100.0f);
        qt3DWindow->camera()->setPosition(QVector3D(2.5, -8, 0.0));
        qt3DWindow->camera()->setViewCenter(QVector3D(0, 0, 0));
    
        // For camera controls
        camController->setLinearSpeed(50.f);
        camController->setLookSpeed(180.f);
        camController->setCamera(qt3DWindow->camera());
    
        // Material
        auto *material = new Qt3DExtras::QPhongAlphaMaterial(rootEntity);
        material->setSpecular(Qt::white);
        material->setShininess(0);
        material->setAmbient(Qt::red);
        material->setAlpha(0.5);
    
        // Transform
        auto *transform = new Qt3DCore::QTransform;
        transform->setScale(1.0f);
    
        auto *customMeshEntity = new Qt3DCore::QEntity(rootEntity);
    
        // Custom Mesh
        auto *customMeshRenderer = new Qt3DRender::QGeometryRenderer;
        auto *customGeometry = new Qt3DRender::QGeometry(customMeshRenderer);
    
        auto *vertexDataBuffer = new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer, customGeometry);
        auto *indexDataBuffer = new Qt3DRender::QBuffer(Qt3DRender::QBuffer::IndexBuffer, customGeometry);
    
        // Vertices
        auto vertexDataPackageSize = nCoordinates*2; // position + normal
        QByteArray vertexBufferData;
        vertexBufferData.resize(vertices.size() * vertexDataPackageSize * sizeof(float));
        auto *rawVertexArray = reinterpret_cast<float *>(vertexBufferData.data());
    
        int idx = 0;
        for (const auto & v : vertices) {
            rawVertexArray[idx++] = float(v.position[0]);
            rawVertexArray[idx++] = float(v.position[1]);
            rawVertexArray[idx++] = float(v.position[2]);
    
            rawVertexArray[idx++] = float(v.normal[0]);
            rawVertexArray[idx++] = float(v.normal[1]);
            rawVertexArray[idx++] = float(v.normal[2]);
        }
        vertexDataBuffer->setData(vertexBufferData);
    
        auto *positionAttribute = new Qt3DRender::QAttribute();
        positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
        positionAttribute->setBuffer(vertexDataBuffer);
        positionAttribute->setDataType(Qt3DRender::QAttribute::Float);
        positionAttribute->setDataSize(nCoordinates);
        positionAttribute->setByteOffset(0);
        positionAttribute->setByteStride(vertexDataPackageSize  * sizeof(float));
        positionAttribute->setCount(vertices.size());
        positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName());
    
        auto *normalAttribute = new Qt3DRender::QAttribute();
        normalAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
        normalAttribute->setBuffer(vertexDataBuffer);
        normalAttribute->setDataType(Qt3DRender::QAttribute::Float);
        normalAttribute->setDataSize(nCoordinates);
        normalAttribute->setByteOffset(nCoordinates * sizeof(float));
        normalAttribute->setByteStride(vertexDataPackageSize * sizeof(float));
        normalAttribute->setCount(vertices.size());
        normalAttribute->setName(Qt3DRender::QAttribute::defaultNormalAttributeName());
    
        QByteArray indexBufferData;
        indexBufferData.resize(triangles.size() * nIndicesPerTriangle * sizeof(ushort));
        auto *rawIndexArray = reinterpret_cast<ushort *>(indexBufferData.data());
        idx = 0;
        for (const auto& t : triangles) {
    
            rawIndexArray[idx++] = t.vertices[0].index;
            rawIndexArray[idx++] = t.vertices[1].index;
            rawIndexArray[idx++] = t.vertices[2].index;
            //std::cout << t.vertices[0].index <<", "<< t.vertices[1].index <<", "<< t.vertices[2].index << std::endl;
        }
        indexDataBuffer->setData(indexBufferData);
    
        auto *indexAttribute = new Qt3DRender::QAttribute();
        indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute);
        indexAttribute->setBuffer(indexDataBuffer);
        indexAttribute->setDataType(Qt3DRender::QAttribute::UnsignedShort);
        indexAttribute->setDataSize(1);
        indexAttribute->setByteOffset(0);
        indexAttribute->setByteStride(0);
        indexAttribute->setCount(triangles.size()*nIndicesPerTriangle);
    
        customMeshRenderer->setInstanceCount(1);
        customMeshRenderer->setFirstVertex(0);
        customMeshRenderer->setIndexOffset(0);
        customMeshRenderer->setFirstInstance(0);
        customMeshRenderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles);
        customMeshRenderer->setGeometry(customGeometry);
        customMeshRenderer->setVertexCount(triangles.size()*nIndicesPerTriangle);
        customGeometry->addAttribute(positionAttribute);
        customGeometry->addAttribute(normalAttribute);
        customGeometry->addAttribute(indexAttribute);
    
        customMeshEntity->addComponent(customMeshRenderer);
        customMeshEntity->addComponent(transform);
        customMeshEntity->addComponent(material);
    
        qt3DWindow->setRootEntity(rootEntity);
        widget->show();
    
        return QApplication::exec();
    }