Понимание меша, созданного Qt3D

Я создаю сетку Qt3D следующим образом:

Qt3DCore::QEntity *newEntity = new Qt3DCore::QEntity();
Qt3DExtras::QConeMesh *mesh =new Qt3DExtras::QConeMesh();
mesh->setTopRadius(0.2);
mesh->setBottomRadius(1.0);
mesh->setLength(2.0);
for(int i = 0; i < mesh->geometry()->attributes().size(); ++i) {
    mesh->geometry()->attributes().at(i)->buffer()->setSyncData(true); // To have access to data
}
newEntity->addComponent(mesh);

Созданная сетка выглядит так:

Конус сетки Конус сетки, другой вид


Далее в коде я пытаюсь экспортировать вышеуказанную сетку в двоичный формат STL. Для этого я извлекаю компоненты геометрии и преобразования объекта:

Qt3DCore::QComponent *compoMesh = nullptr; // place holder for mesh geometry of entity
Qt3DCore::QComponent *compoTran = nullptr; // place holder for mesh transformation of entity
QVector<Qt3DCore::QComponent *> compos = newEntity->components();
for(int i = 0; i < compos.size(); ++i) {
    if (qobject_cast<Qt3DRender::QGeometryRenderer *>(compos.at(i))) {
        compoMesh = compos.at(i); // mesh geometry component
    } else if (qobject_cast<Qt3DCore::QTransform *>(compos.at(i))) {
        compoTran = compos.at(i); // mesh transformation component
    }
}

Затем я получаю данные буфера, содержащие положения вершин и нормали:

Qt3DRender::QGeometryRenderer *mesh = qobject_cast<Qt3DRender::QGeometryRenderer *>(compoMesh);
Qt3DRender::QGeometry *geometry = mesh->geometry();
QVector<Qt3DRender::QAttribute *> atts = geometry->attributes();

Теперь мы сосредоточимся на атрибуте положения вершины и атрибуте нормали вершины. Мы получаем смещение в байтах и шаг в байтах для каждого, а также проверяем, используют ли оба буфера одинаковые данные:

for(int i = 0; i < atts.size(); ++i) {
        if(atts.at(i)->name() == Qt3DRender::QAttribute::defaultPositionAttributeName()) {
            byteOffsetPos = atts.at(i)->byteOffset();
            byteStridePos = atts.at(i)->byteStride();
            bufferPtrPos = atts.at(i)->buffer();
        } else if(atts.at(i)->name() == Qt3DRender::QAttribute::defaultNormalAttributeName()) {
            byteOffsetNorm = atts.at(i)->byteOffset();
            byteStrideNorm = atts.at(i)->byteStride();
            bufferPtrNorm = atts.at(i)->buffer();
        }
    }
if(bufferPtrPos != bufferPtrNorm) {
    qDebug() << __func__ << "!!! Buffer pointer for position and normal are NOT the same";
    // Throw error here
    }

Затем я использую смещение байта и шаг байта, чтобы извлечь треугольники и записать их в файл STL. Однако экспортированный STL НЕ хорош:

Экспортированный STL НЕ хорош


Я использую тот же код для экспорта STL пользовательских сеток, который отлично работает. Однако, когда я использую тот же код для экспорта готовых ячеек Qt3D, таких как QConeMesh Экспортированный STL НЕ приемлем. Кто-нибудь может дать мне подсказку.


ОБНОВИТЬ

Как отметил @vre, я собираюсь опубликовать оставшуюся часть кода для записи треугольников в файл STL. Это большой код, я стараюсь изо всех сил, чтобы он был ясным и лаконичным:

Для получения положения треугольника и нормалей я перебираю атрибуты и получаю VertexBuffer буфер, в котором хранятся все позиции и нормали:

// I loop over attributes to get access to VertexBuffer buffer
for(int i = 0; i < atts.size(); ++i) {
    Qt3DRender::QBuffer *buffer = atts.at(i)->buffer();
    QByteArray data = buffer->data();
    // We focus on VertexBuffer, NOT IndexBuffer!
    if( buffer->type() == Qt3DRender::QBuffer::VertexBuffer ) {
        // Number of triangles is number of vertices divided by 3:
        quint32 trianglesCount = atts.at(i)->count() / 3;

        // For each triangle, extract vertex positions and normals
        for(int j = 0; j < trianglesCount; ++j) {
            // Index for each triangle positions data
            // Each triangle has 3 vertices, hence 3 factor:
            // We already know byte-offset and byte-stride for positions
            int idxPos  = byteOffsetPos  + j * 3 * byteStridePos ;
            // Index for each triangle normals data
            // Each tirangle has 3 normals (right?), hence 3 factor:
            // We already know byte-offset and byte-stride for normals
            int idxNorm = byteOffsetNorm + j * 3 * byteStrideNorm;

            // Get x, y, z positions for 1st vertex
            // I have already checked that attribute base type is float by: `atts.at(i)->vertexBaseType();`
            QByteArray pos0x = data.mid(idxPos + 0 * sizeof(float), sizeof(float));
            QByteArray pos0y = data.mid(idxPos + 1 * sizeof(float), sizeof(float));
            QByteArray pos0z = data.mid(idxPos + 2 * sizeof(float), sizeof(float));

            // Get x, y z for 1st normal
            QByteArray norm0x= data.mid(idxNorm + 0 * sizeof(float), sizeof(float));
            QByteArray norm0y= data.mid(idxNorm + 1 * sizeof(float), sizeof(float));
            QByteArray norm0z= data.mid(idxNorm + 2 * sizeof(float), sizeof(float));

            // Get x, y, z positions for 2nd vertex
            QByteArray pos1x = data.mid(idxPos  + 1 * byteStridePos + 0 * sizeof(float), sizeof(float));
            QByteArray pos1y = data.mid(idxPos  + 1 * byteStridePos + 1 * sizeof(float), sizeof(float));
            QByteArray pos1z = data.mid(idxPos  + 1 * byteStridePos + 2 * sizeof(float), sizeof(float));

            // Get x, y, z for 2nd normal
            QByteArray norm1x= data.mid(idxNorm + 1 * byteStrideNorm + 0 * sizeof(float), sizeof(float));
            QByteArray norm1y= data.mid(idxNorm + 1 * byteStrideNorm + 1 * sizeof(float), sizeof(float));
            QByteArray norm1z= data.mid(idxNorm + 1 * byteStrideNorm + 2 * sizeof(float), sizeof(float));

            // Get x, y, z positions for 3rd vertex
            QByteArray pos2x = data.mid(idxPos  + 2 * byteStridePos + 0 * sizeof(float), sizeof(float));
            QByteArray pos2y = data.mid(idxPos  + 2 * byteStridePos + 1 * sizeof(float), sizeof(float));
            QByteArray pos2z = data.mid(idxPos  + 2 * byteStridePos + 2 * sizeof(float), sizeof(float));

            // Get x, y, z for 3rd normal
            QByteArray norm2x= data.mid(idxNorm + 2 * byteStrideNorm+ 0 * sizeof(float), sizeof(float));
            QByteArray norm2y= data.mid(idxNorm + 2 * byteStrideNorm+ 1 * sizeof(float), sizeof(float));
            QByteArray norm2z= data.mid(idxNorm + 2 * byteStrideNorm+ 2 * sizeof(float), sizeof(float));

            // Convert x, y, z byte arrays into floats
            float floatPos0x;
            if ( pos0x.size() >= sizeof(floatPos0x) ) {
                floatPos0x = *reinterpret_cast<const float *>( pos0x.data() );
            }
            float floatPos0y;
            if ( pos0y.size() >= sizeof(floatPos0y) ) {
                floatPos0y = *reinterpret_cast<const float *>( pos0y.data() );
            }
            float floatPos0z;
            if ( pos0z.size() >= sizeof(floatPos0z) ) {
                floatPos0z = *reinterpret_cast<const float *>( pos0z.data() );
            }

            // Do the rest of byte-array to float conversions:
            // norm0x=>floatNorm0x, norm0y=>floatNorm0y, norm0z=>floatNorm0z
            // pos1x=>floatPos1x, pos1y=>floatPos1y, pos1z=>floatPos1z
            // norm1x=>floatNorm1x, norm1y=>floatNorm1y, norm1z=>floatNorm1z
            // pos2x=>floatPos2x, pos2y=>floatPos2y, pos2z=>floatPos2z
            // norm2x=>floatNorm2x, norm2y=>floatNorm2y, norm2z=>floatNorm2z

            // Compose positions matrix before applying transformations
            // I'm going to use `QMatrix4x4` but I have 3 vertices of 3x1
            // Therefore I have to fill out `QMatrix4x4` with zeros and ones
            // Please see this question and its answer: https://stackru.com/q/51979168/3405291
            QMatrix4x4 floatPos4x4 = QMatrix4x4(
                floatPos0x, floatPos1x, floatPos2x, 0,
                floatPos0y, floatPos1y, floatPos2y, 0,
                floatPos0z, floatPos1z, floatPos2z, 0,
                1         , 1         , 1         , 0
            );

            // Apply transformations to positions:
            // We already have transformations component `compoTran` from previous code:
            Qt3DCore::QTransform *tran = qobject_cast<Qt3DCore::QTransform *>(compoTran);
            QMatrix4x4 newFloatPos4x4 = tran->matrix() * floatPos4x4;

            // Get new positions after applying transformations:
            float newFloatPos0x = newFloatPos4x4(0,0);
            float newFloatPos0y = newFloatPos4x4(1,0);
            float newFloatPos0z = newFloatPos4x4(2,0);

            float newFloatPos1x = newFloatPos4x4(0,1);
            float newFloatPos1y = newFloatPos4x4(1,1);
            float newFloatPos1z = newFloatPos4x4(2,1);

            float newFloatPos2x = newFloatPos4x4(0,2);
            float newFloatPos2y = newFloatPos4x4(1,2);
            float newFloatPos2z = newFloatPos4x4(2,2);

            // Convert all the floats (after applying transformations) back to byte array:
            QByteArray newPos0x( reinterpret_cast<const char *>( &newFloatPos0x ), sizeof( newFloatPos0x ) );
            QByteArray newPos0y( reinterpret_cast<const char *>( &newFloatPos0y ), sizeof( newFloatPos0y ) );
            QByteArray newPos0z( reinterpret_cast<const char *>( &newFloatPos0z ), sizeof( newFloatPos0z ) );

            QByteArray newPos1x( reinterpret_cast<const char *>( &newFloatPos1x ), sizeof( newFloatPos1x ) );
            QByteArray newPos1y( reinterpret_cast<const char *>( &newFloatPos1y ), sizeof( newFloatPos1y ) );
            QByteArray newPos1z( reinterpret_cast<const char *>( &newFloatPos1z ), sizeof( newFloatPos1z ) );

            QByteArray newPos2x( reinterpret_cast<const char *>( &newFloatPos2x ), sizeof( newFloatPos2x ) );
            QByteArray newPos2y( reinterpret_cast<const char *>( &newFloatPos2y ), sizeof( newFloatPos2y ) );
            QByteArray newPos2z( reinterpret_cast<const char *>( &newFloatPos2z ), sizeof( newFloatPos2z ) );

            // Log triangle vertex positions and normals (float numbers)
            // A sample log is posted on this question on Stackru
            qDebug() << __func__ << " pos 0: x " << newFloatPos0x << " y " << newFloatPos0y << " z " << newFloatPos0z;
            qDebug() << __func__ << " pos 1: x " << newFloatPos1x << " y " << newFloatPos1y << " z " << newFloatPos1z;
            qDebug() << __func__ << " pos 2: x " << newFloatPos2x << " y " << newFloatPos2y << " z " << newFloatPos2z;

            qDebug() << __func__ << " norm 0: x " << floatNorm0x << " y " << floatNorm0y << " z " << floatNorm0z;
            qDebug() << __func__ << " norm 1: x " << floatNorm1x << " y " << floatNorm1y << " z " << floatNorm1z;
            qDebug() << __func__ << " norm 2: x " << floatNorm2x << " y " << floatNorm2y << " z " << floatNorm2z;

            // Write the triangle to STL file
            // Note that STL file needs a header which is written in another section of code
            // Note that STL file needs total number of triangles which is written in another section of code
            // Note that STL file needs only one normal vector for each triangle, but here we have 3 normals (for 3 vertices), therefore I'm writing only the 1st normal to STL (is it OK?!)
            // `baStl` is a byte-array containing all the STL data
            // `baStl` byte-array is written to a file in another section of the code
            QBuffer tempBuffer(&baStl);
            tempBuffer.open(QIODevice::Append);
            tempBuffer.write( norm0x   ); // vertex 0 Normal vector
            tempBuffer.write( norm0y   );
            tempBuffer.write( norm0z   );
            tempBuffer.write( newPos0x ); // New vertex 0 position
            tempBuffer.write( newPos0y );
            tempBuffer.write( newPos0z );
            tempBuffer.write( newPos1x ); // New vertex 1 position
            tempBuffer.write( newPos1y );
            tempBuffer.write( newPos1z );
            tempBuffer.write( newPos2x ); // New vertex 2 position
            tempBuffer.write( newPos2y );
            tempBuffer.write( newPos2z );
            tempBuffer.write("aa"); // Attribute byte count: UINT16: 2 bytes: content doesn't matter, just write 2 bytes
            tempBuffer.close();
        }
    }
}

Приведенный выше код прекрасно работает для пользовательских сеток. Я имею в виду, что когда я импортирую файл STL в свое приложение Qt3D, а затем снова экспортирую его как STL, экспортированный STL хорош. Проблема в том, что при создании Qt3D готовые сетки вроде QConeMesh Экспортированный STL облажался, я имею в виду, что общая геометрия в порядке, но треугольники запутаны, как показано на рисунке выше.

Мой код записывает следующие значения при попытке экспортировать QConeMesh, Как видно, нормальные векторы имеют размер блока, который показывает, что они на самом деле являются нормальными:

... exportStlUtil pos 0: x -10.6902 y -7.55854 z 4.76837e-07 exportStlUtil pos 1: x -12.8579 y -4.31431 z 2.98023e-07 exportStlUtil pos 2: x -13.6191 y -0.487476 z 5.96046e-08 exportStlUtil норма 0: x -0,707107 y 0 z 0,707107 exportStlUtil норма 1: x -0,92388 y 0 z 0,382683 exportStlUtil норма 2: x -1 y 0 z -8,74228e-08 exportStlUtil полож. 0: x -12,8579  y  3.33936  z  -1.19209e-07
exportStlUtil  pos 1: x  -10.6902  y  6.58359  z  -3.57628e-07
exportStlUtil  pos 2: x  -7.44594  y  8.75132  z  -4.76837e-07
exportStlUtil норма 0: x -0,92388 y 0 z -0,382683 exportStlUtil норма 1: x -0,7077 0 z -0,707107 exportStlUtil норма 2: x -0,382683 y 0 z -0,92388 exportStlUtil pos 0: x -3,61911 y 9,51252  z  -4,76837e-07 exportStlUtil pos 1: x 0,207723 y 8,75132 z -4,76837e-07
exportStlUtil  pos 2: x  3.45196  y  6.58359  z  -3.57628e-07
exportStlUtil норма 0: x 1.19249e-08 y 0 z -1 exportStlUtil норма 1: x 0,382684 y 0 z -0,923879 exportStlUtil норма 2: x 0,707107 y 0 z -0,707107
exportStlUtil  pos 0: x  5.61968  y  3.33936  z  -1.19209e-07
exportStlUtil  pos 1: x  6.38089  y  -0.487479  z  5.96046e-08
exportStlUtil  pos 2: x  6.38089  y  -0.487477  z  0.133333
exportStlUtil норма 0: x 0,92388 y 0 z -0,382683 exportStlUtil норма 1: x 1 y 0 z 1,748 -07 exportStlUtil норма 2: x  1  y  0  z  0
exportStlUtil  pos 0: x  5.61968  y  -4,31431 z 0,133334 exportStlUtil pos 1: x 3,45195  y  -7,55854  z  0,133334 exportStlUtil pos 2: x 0,207721 y -9,72627 z 0,133334 exportStlUtil норма 0,92388 y 0 z 0,382683 exportStlUtil норма 1: х 0,707107 y 0 z 0,707107 exportStlUtil норма 2: x 0,382683 y 0 z 0,92388... 

1 ответ

Решение

Переформулировка моих комментариев в качестве ответа:

Геометрия Qt3D по умолчанию состоит в основном из по крайней мере двух буферов: vertexBuffer, содержащий вершины, координаты текстуры, а также нормалей, и indexBuffer, содержащий индексы, которые образуют треугольники или triangleStrips. Чтобы получить доступ к вершине или нормали в vertexBuffer, вы сначала ищите последовательность из трех последовательных индексов из indexBuffer и вычисляете смещение в vertexBuffer с учетом vertexSize, byteStride и byteOffset.

Для доступа к vertexCoord posx в vertexBuffer макета [vertexCoords, textureCoords, normalCoords] (GeometryRenderer имеет треугольники primitiveType), вычисление vertexBufferIndex будет таким:

vertexBufPtr + indexBuffer(i) * byteStride + byteOffsetPos

и для первой нормальной координаты

vertexBufPtr + indexBuffer(i) * byteStride + byteOffsetNormal,

byteStride равен 8 * sizeof(float), byteOffsetPos равен 0, а byteOffsetNormal равен 5 * sizeof(float).

Другие вопросы по тегам