diff --git a/plugin/GeometryProvider.cpp b/plugin/GeometryProvider.cpp new file mode 100644 index 0000000..42ac407 --- /dev/null +++ b/plugin/GeometryProvider.cpp @@ -0,0 +1,340 @@ +#include "GeometryProvider.h" +GeometryProvider::GeometryProvider(QQuick3DObject *parent) : QQuick3DGeometry{parent} +{ + +} + +void GeometryProvider::setHasNormals(bool hasNormals) +{ + if(m_hasNormals != hasNormals) + { + m_hasNormals = hasNormals; + Q_EMIT hasNormalsChanged(); + } +} + +void GeometryProvider::setHasUv(bool hasUv) +{ + if(m_hasUV != hasUv) + { + m_hasUV = hasUv; + Q_EMIT hasUvChanged(); + } +} + +void GeometryProvider::setUVAdjust(qreal adjust) +{ + if(m_uvAdjust != adjust) + { + m_uvAdjust = adjust; + Q_EMIT uvAdjustChanged(); + } +} + +void GeometryProvider::setSource(QString &source) +{ + QFile file(source); + QFileInfo fileInfo(file); + + if(!fileInfo.exists()) + { + qWarning() << QLatin1String("File %1 does not exist").arg(fileInfo.absoluteFilePath()); + return; + } + + if(fileInfo.suffix() == QLatin1String("obj")) + loadObj(file); + + else if(fileInfo.suffix() == QLatin1String("stl")) + loadStl(file); +} + +void GeometryProvider::loadObj(QFile &file) +{ + // 1e9 = 1*10^9 = 1,000,000,000 + QVector3D boundsMin( 1e9, 1e9, 1e9); + QVector3D boundsMax(-1e9,-1e9,-1e9); + setHasNormals(false); + setHasUv(false); + + QTextStream in(&file); + while (!in.atEnd()) { + QString input = in.readLine(); + // # means comment + if (input.isEmpty() || input[0] == QLatin1Char('#')) + continue; + + QTextStream ts(&input); + QString id; + ts >> id; + //--------------- v = List of vertices with (x,y,z[,w]) corrdinates. ----------------- + if (id == QLatin1String("v")) { + QVector3D p; + for (int i = 0; i < 3; ++i) + { + ts >> p[i]; + boundsMin[i] = qMin(boundsMin[i], p[i]); + boundsMax[i] = qMax(boundsMax[i], p[i]); + } + m_vertices << p; + + //--------------- f = Face definitions ----------------- + } else if (id == QLatin1String("f") || id == QLatin1String("fo")) { + QVarLengthArray p; + + while (!ts.atEnd()) { + QString vertex; + ts >> vertex; + //e.g. vertex / texture + // vertex index in correspondence with vertex list. + const int vertexIndex = vertex.split(QLatin1Char('/')).value(0).toInt(); +// qDebug() << "> vertexIndex : " << vertexIndex; + if (vertexIndex) { + p.append((vertexIndex > 0) ? (vertexIndex - 1) : (m_vertices.size() + vertexIndex)); +// int d = (vertexIndex > 0) ? (vertexIndex - 1) : (m_vertices.size() + vertexIndex); +// qDebug() << "selected index : " << d; + } + } + + qDebug() << QLatin1String("p.size(%1) vs. vertexIndices.size(%2)").arg(QString::number(p.size())).arg(QString::number(m_vertexIndices.size())); + + for (int i = 0; i < p.size(); ++i) { + const int edgeA = p[i]; + const int edgeB = p[(i + 1) % p.size()]; +// qDebug() << QString("edgeA(%1/%2), edgeB(%3/%4)").arg(QString::number(edgeA), QString::number(i), QString::number(edgeB), QString::number((i + 1) % p.size())) ; + + if (edgeA < edgeB) { + m_edgeIndices << edgeA << edgeB; +// qDebug() << "Added : edgeA : " << edgeA << " / edgeB : " << edgeB ; + } + } + + // append vertex / texture-coordinate / normal + for (int i = 0; i < 3; ++i) + m_vertexIndices << p[i]; + + if (p.size() == 4) + for (int i = 0; i < 3; ++i) + m_vertexIndices << p[(i + 2) % 4]; + } + } + + qDebug() << QLatin1String("size(%1), max-x(%2), min-x(%3), max-y(%4), min-y(%5), max-z(%6), min-z(%7)") + .arg(QString::number(m_vertices.size()), + QString::number(boundsMax.x()), + QString::number(boundsMin.x()), + QString::number(boundsMax.y()), + QString::number(boundsMin.y()), + QString::number(boundsMax.z()), + QString::number(boundsMin.z())); + const QVector3D bounds = boundsMax - boundsMin; + const qreal scale = 1 / qMax(bounds.x(), qMax(bounds.y(), bounds.z())); + qDebug() << QLatin1String("scale(%1) = 1 / %2") + .arg(QString::number(scale), QString::number(qMax(bounds.x(), qMax(bounds.y(), bounds.z())))); +// for (int i = 0; i < m_vertices.size(); ++i) { +// //the way to place the model by mutiplying the ratio. +// float ratio = 0.f; +// m_vertices[i] = (m_vertices[i] - (boundsMin + bounds * ratio)) * scale; +// } + + m_verticesNew = m_vertices; + recomputeAll(); +} + +void GeometryProvider::loadStl(QFile &file) +{ + QTextStream stream(&file); + const QString &head = stream.readLine(); + setHasUv(false); + if (head.left(6) == QLatin1String("solid ") && head.size() < 80) // ASCII format + { +// name = head.right(head.size() - 6).toStdString(); + QString word; + stream >> word; + for(; word == QLatin1String("facet") ; stream >> word) + { + stream >> word; // normal x y z + QVector3D n; + stream >> n[0] >> n[1] >> n[2]; + n.normalize(); + + stream >> word >> word; // outer loop + stream >> word; + size_t startIndex = m_vertices.size(); + for(; word != QLatin1String("endloop") ; stream >> word) + { + QVector3D v; //vertex x y z + stream >> v[0] >> v[1] >> v[2]; + m_vertices.push_back(v); +// qDebug() << "==== outer loop ==="; + } + + for(qsizetype i = startIndex + 2 ; i < m_vertices.size() ; ++i) + { + m_vertexIndices.push_back(startIndex); + m_vertexIndices.push_back(i - 1); + m_vertexIndices.push_back(i); + + qDebug() << QLatin1String("edgeA(%1), edgeB(%2)").arg(QString::number(startIndex), QString::number(i)) ; + +// if (startIndex < (i-1)) + m_edgeIndices << (startIndex) << (i - 1) << i; + } + stream >> word; // endfacet + setHasNormals(false); + } + } else { + file.setTextModeEnabled(false); + file.seek(80); + quint32 triangleCount; + file.read((char*)&triangleCount, sizeof(triangleCount)); + m_vertices.reserve(triangleCount * 3U); + m_normals.reserve(triangleCount * 3U); + m_vertexIndices.reserve(triangleCount * 3U); + for(size_t i = 0 ; i < triangleCount ; ++i) + { + QVector3D n, a, b, c; + + READ_VECTOR(n); + READ_VECTOR(a); + READ_VECTOR(b); + READ_VECTOR(c); + + m_vertexIndices.push_back(m_vertices.size()); + m_vertexIndices.push_back(m_vertices.size() + 1); + m_vertexIndices.push_back(m_vertices.size() + 2); + m_vertices.push_back(a); + m_vertices.push_back(b); + m_vertices.push_back(c); + + quint16 attribute_byte_count; + file.read((char*)&attribute_byte_count, sizeof(attribute_byte_count)); + } + setHasNormals(true); + } + m_verticesNew = m_vertices; + recomputeAll(); +} + +//Bounding Box : http://en.wikibooks.org/wiki/OpenGL_Programming/Bounding_box +void GeometryProvider::recomputeAll() +{ + qDebug() << Q_FUNC_INFO; + + //calculate normals of each face + int size = m_verticesNew.size(); + m_normals.resize(size); + for (int i = 0; i < m_vertexIndices.size(); i += 3) { + const QVector3D a = m_verticesNew.at(m_vertexIndices.at(i)); + const QVector3D b = m_verticesNew.at(m_vertexIndices.at(i+1)); + const QVector3D c = m_verticesNew.at(m_vertexIndices.at(i+2)); + + const QVector3D normal = QVector3D::crossProduct(b - a, c - a).normalized(); + + for (int j = 0; j < 3; ++j) + m_normals[m_vertexIndices.at(i + j)] += normal; + } + + /* //debug output + qDebug() << "=========== Output ============="; + for (int i = 0; i < m_verticesNew.size(); ++i) { + qDebug() << QString("x(%1), y(%2), z(%3)").arg(m_verticesNew.at(i).x()).arg(m_verticesNew.at(i).y()).arg(m_verticesNew.at(i).z()); + } + + for (int i = 0; i< m_vertexIndices.size(); ++i) { + qDebug() << QString("index %1").arg(m_vertexIndices.at(i)); + } + + for (int i = 0; i < m_normals.size(); ++i ) { + qDebug() << QString("x(%1), y(%2), z(%3)").arg(m_normals.at(i).x()).arg(m_normals.at(i).y()).arg(m_normals.at(i).z()); + } + qDebug() << "========================"; + */ + //recompute normals and bounding volume + qreal minX, maxX, + minY, maxY, + minZ, maxZ; + + minX = maxX = m_verticesNew[0].x(); + minY = maxY = m_verticesNew[0].y(); + minZ = maxZ = m_verticesNew[0].z(); + + for (int i = 0; i < size; ++i) + { + //compute normals + m_normals[i] = m_normals[i].normalized(); + + //calculate values of maximum and minimum + if (m_verticesNew[i].x() < minX) minX = m_verticesNew[i].x(); + if (m_verticesNew[i].x() > maxX) maxX = m_verticesNew[i].x(); + if (m_verticesNew[i].y() < minY) minY = m_verticesNew[i].y(); + if (m_verticesNew[i].y() > maxY) maxY = m_verticesNew[i].y(); + if (m_verticesNew[i].z() < minZ) minZ = m_verticesNew[i].z(); + if (m_verticesNew[i].z() > maxZ) maxZ = m_verticesNew[i].z(); + } + + m_size = QVector3D(maxX-minX, maxY-minY, maxZ-minZ); + m_center = QVector3D((minX+maxX)/2, (minY+maxY)/2, (minZ+maxZ)/2); + + m_min = QVector3D(minX, minY, minZ); + m_max = QVector3D(maxX, maxY, maxZ); + + // qDebug() << QLatin1String("MIN : x(%1), y(%2), z(%3)").arg(m_min.x()).arg(m_min.y()).arg(m_min.z()); + // qDebug() << QLatin1String("MAN : x(%1), y(%2), z(%3)").arg(m_max.x()).arg(m_max.y()).arg(m_max.z()); + // qDebug() << QLatin1String("SIZE : x(%1), y(%2), z(%3)").arg(m_size.x()).arg(m_size.y()).arg(m_size.z()); + +// QMatrix4x4 center(1,1,1,1), scale(1,1,1,1); +// m_transform.translate(QMatrix4x4(1,1,1) * center) + + int stride = 3 * sizeof(qreal); + + if(hasNormals()) + stride += 3 * sizeof(qreal); + if(hasUv()) + stride += 2 * sizeof(qreal); + + QByteArray vertexData(3 * stride, Qt::Uninitialized); + qreal *pointer = reinterpret_cast(vertexData.data()); + + for(int i = 0; i < m_vertices.count(); i++) + { + *pointer++ = m_vertices.at(i).x(); + *pointer++ = m_vertices.at(i).y(); + *pointer++ = m_vertices.at(i).z(); + + if(hasNormals()) + { + *pointer++ = m_normals.at(i).x(); + *pointer++ = m_normals.at(i).y(); + *pointer++ = m_normals.at(i).z(); + } + + if(hasUv()) + { + *pointer++ = m_uv.at(i).x() - m_uvAdjust; + *pointer++ = m_uv.at(i).y() - m_uvAdjust; + } + } + + setBounds(m_min, m_max); + setVertexData(vertexData); + setStride(stride); + + setPrimitiveType(QQuick3DGeometry::PrimitiveType::Points); + + addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, + 0, + QQuick3DGeometry::Attribute::F32Type); + + if (m_hasNormals) { + addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, + 3 * sizeof(float), + QQuick3DGeometry::Attribute::F32Type); + } + + if (m_hasUV) { + addAttribute(QQuick3DGeometry::Attribute::TexCoordSemantic, + m_hasNormals ? 6 * sizeof(float) : 3 * sizeof(float), + QQuick3DGeometry::Attribute::F32Type); + } +} \ No newline at end of file diff --git a/plugin/GeometryProvider.h b/plugin/GeometryProvider.h new file mode 100644 index 0000000..9d6d966 --- /dev/null +++ b/plugin/GeometryProvider.h @@ -0,0 +1,113 @@ +/* + * Komplex Wallpaper Engine + * Copyright (C) 2025 @DigitalArtifex | github.com/DigitalArtifex + * + * GeometryProvider.h + * + * This class provides a way to use .obj and .stl in QML + * + * The loadObj() and loadStl() functions are from stl-gcode-viewer + * Copyright 2015-2025 @sokunmin | github.com/sokunmin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + */ +#ifndef GeometryProvider_H +#define GeometryProvider_H + +#define READ_VECTOR(v)\ +do {\ + float f;\ + file.read((char*)&f, sizeof(float)); v[0] = f;\ + file.read((char*)&f, sizeof(float)); v[1] = f;\ + file.read((char*)&f, sizeof(float)); v[2] = f;\ +} while(false) + +#include +#include +#include +#include +#include +#include + +#include "Komplex_global.h" + +class KOMPLEX_EXPORT GeometryProvider : public QQuick3DGeometry +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(bool hasNormals READ hasNormals WRITE setHasNormals NOTIFY hasNormalsChanged) + Q_PROPERTY(bool hasUv READ hasUv WRITE setHasUv NOTIFY hasUvChanged) + Q_PROPERTY(qreal uvAdjust READ uvAdjust WRITE setUVAdjust NOTIFY uvAdjustChanged) + Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged) + +public: + enum State + { + Idle, + Loading, + Loaded, + Error + }; + + GeometryProvider(QQuick3DObject *parent = nullptr); + + bool hasNormals() const { return m_hasNormals; } + void setHasNormals(bool enable); + + bool hasUv() const { return m_hasUV; } + void setHasUv(bool enable); + + float uvAdjust() const { return m_uvAdjust; } + void setUVAdjust(qreal f); + + QString source() const { return m_source; } + void setSource(QString &source); + +Q_SIGNALS: + void normalsChanged(); + void hasNormalsChanged(); + void uvChanged(); + void hasUvChanged(); + void uvAdjustChanged(); + void sourceChanged(); + +private: + void loadObj(QFile &file); + void loadStl(QFile &file); + void recomputeAll(); + + bool m_hasNormals = false; + bool m_hasUV = false; + + qreal m_uvAdjust = 0.0f; + QString m_source; + + QVector3D m_size; + QVector3D m_center; + QVector3D m_min; + QVector3D m_max; + QMatrix4x4 m_transform; + + QVector m_vertices; + QVector m_verticesNew; + QVector m_normals; + QVector m_uv; + + QVector m_edgeIndices; + QVector m_vertexIndices; +}; + +Q_DECLARE_METATYPE(GeometryProvider) +#endif \ No newline at end of file