diff --git a/dust3d.pro b/dust3d.pro index 60f56e1d..c4fc6ba1 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -224,6 +224,27 @@ HEADERS += src/motionpreviewsgenerator.h SOURCES += src/animationclipplayer.cpp HEADERS += src/animationclipplayer.h +SOURCES += src/texturetype.cpp +HEADERS += src/texturetype.h + +SOURCES += src/imageforever.cpp +HEADERS += src/imageforever.h + +SOURCES += src/materialeditwidget.cpp +HEADERS += src/materialeditwidget.h + +SOURCES += src/materiallistwidget.cpp +HEADERS += src/materiallistwidget.h + +SOURCES += src/materialmanagewidget.cpp +HEADERS += src/materialmanagewidget.h + +SOURCES += src/materialpreviewsgenerator.cpp +HEADERS += src/materialpreviewsgenerator.h + +SOURCES += src/materialwidget.cpp +HEADERS += src/materialwidget.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/resources.qrc b/resources.qrc index 12c324c5..ea6258e0 100644 --- a/resources.qrc +++ b/resources.qrc @@ -6,6 +6,7 @@ resources/tree-branch-more.png resources/tree-branch-open.png resources/tree-vline.png + resources/material-demo-model.ds3 shaders/default.vert shaders/default.frag shaders/default.core.vert diff --git a/resources/material-demo-model.ds3 b/resources/material-demo-model.ds3 new file mode 100644 index 00000000..be193198 Binary files /dev/null and b/resources/material-demo-model.ds3 differ diff --git a/shaders/default.core.vert b/shaders/default.core.vert index 876b2ba6..c607e107 100644 --- a/shaders/default.core.vert +++ b/shaders/default.core.vert @@ -5,6 +5,7 @@ in vec3 color; in vec2 texCoord; in float metalness; in float roughness; +in vec3 tangent; out vec3 vert; out vec3 vertNormal; out vec3 vertColor; diff --git a/shaders/default.vert b/shaders/default.vert index dae66df8..1145ccd5 100644 --- a/shaders/default.vert +++ b/shaders/default.vert @@ -4,6 +4,7 @@ attribute vec3 color; attribute vec2 texCoord; attribute float metalness; attribute float roughness; +attribute vec3 tangent; varying vec3 vert; varying vec3 vertNormal; varying vec3 vertColor; @@ -11,17 +12,50 @@ varying vec2 vertTexCoord; varying float vertMetalness; varying float vertRoughness; varying vec3 cameraPos; +varying vec3 firstLightPos; +varying vec3 secondLightPos; +varying vec3 thirdLightPos; uniform mat4 projectionMatrix; uniform mat4 modelMatrix; +uniform mat3 normalMatrix; uniform mat4 viewMatrix; +uniform highp int normalMapEnabled; + +mat3 transpose(mat3 m) +{ + return mat3(m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); +} + void main() { vert = (modelMatrix * vertex).xyz; vertNormal = normalize((modelMatrix * vec4(normal, 1.0)).xyz); vertColor = color; + cameraPos = vec3(0, 0, -2.1); + + firstLightPos = vec3(5.0, 5.0, 5.0); + secondLightPos = vec3(-5.0, 5.0, 5.0); + thirdLightPos = vec3(0.0, -5.0, -5.0); + + gl_Position = projectionMatrix * viewMatrix * vec4(vert, 1.0); + + if (normalMapEnabled == 1) { + vec3 T = normalize(normalMatrix * tangent); + vec3 N = normalize(normalMatrix * normal); + T = normalize(T - dot(T, N) * N); + vec3 B = cross(N, T); + + mat3 TBN = transpose(mat3(T, B, N)); + firstLightPos = TBN * firstLightPos; + secondLightPos = TBN * secondLightPos; + thirdLightPos = TBN * thirdLightPos; + cameraPos = TBN * cameraPos; + vert = TBN * vert; + } + vertTexCoord = texCoord; vertMetalness = metalness; vertRoughness = roughness; - cameraPos = vec3(0, 0, -2.1); - gl_Position = projectionMatrix * viewMatrix * vec4(vert, 1.0); } \ No newline at end of file diff --git a/shaders/pbr-qt.frag b/shaders/pbr-qt.frag index 22f603d1..ee15bfcd 100644 --- a/shaders/pbr-qt.frag +++ b/shaders/pbr-qt.frag @@ -63,9 +63,18 @@ varying highp vec2 vertTexCoord; varying highp float vertMetalness; varying highp float vertRoughness; varying highp vec3 cameraPos; +varying vec3 firstLightPos; +varying vec3 secondLightPos; +varying vec3 thirdLightPos; uniform highp vec3 lightPos; uniform highp sampler2D textureId; uniform highp int textureEnabled; +uniform highp sampler2D normalMapId; +uniform highp int normalMapEnabled; +uniform highp sampler2D metalnessRoughnessAmbientOcclusionMapId; +uniform highp int metalnessMapEnabled; +uniform highp int roughnessMapEnabled; +uniform highp int ambientOcclusionMapEnabled; const int MAX_LIGHTS = 8; const int TYPE_POINT = 0; @@ -275,7 +284,6 @@ void main() // FIXME: don't hard code here exposure = 0.0; gamma = 2.2; - const highp float vertAmbientOcclusion = 1.0; // Light settings: // https://doc-snapshots.qt.io/qt5-5.12/qt3d-pbr-materials-lights-qml.html @@ -283,7 +291,7 @@ void main() // Key light lights[0].type = TYPE_POINT; - lights[0].position = vec3(5.0, 5.0, 5.0); + lights[0].position = firstLightPos; lights[0].color = vec3(0.588, 0.588, 0.588); lights[0].intensity = 5.0; lights[0].constantAttenuation = 0.0; @@ -292,7 +300,7 @@ void main() // Fill light lights[1].type = TYPE_POINT; - lights[1].position = vec3(-5.0, 5.0, 5.0); + lights[1].position = secondLightPos; lights[1].color = vec3(0.588, 0.588, 0.588); lights[1].intensity = 3.0; lights[1].constantAttenuation = 0.0; @@ -301,7 +309,7 @@ void main() // Rim light lights[2].type = TYPE_POINT; - lights[2].position = vec3(0.0, -5.0, -5.0); + lights[2].position = thirdLightPos; lights[2].color = vec3(0.588, 0.588, 0.588); lights[2].intensity = 2.5; lights[2].constantAttenuation = 0.0; @@ -314,13 +322,34 @@ void main() } color = pow(color, vec3(gamma)); - float roughness = min(0.99, vertRoughness); + highp vec3 normal = vertNormal; + if (normalMapEnabled == 1) { + normal = texture2D(normalMapId, vertTexCoord).rgb; + normal = normalize(normal * 2.0 - 1.0); + } + + float metalness = vertMetalness; + if (metalnessMapEnabled == 1) { + metalness = texture2D(metalnessRoughnessAmbientOcclusionMapId, vertTexCoord).b / 255.0; + } + + float roughness = vertRoughness; + if (roughnessMapEnabled == 1) { + roughness = texture2D(metalnessRoughnessAmbientOcclusionMapId, vertTexCoord).r / 255.0; + } + + float ambientOcclusion = 1.0; + if (ambientOcclusionMapEnabled == 1) { + ambientOcclusion = texture2D(metalnessRoughnessAmbientOcclusionMapId, vertTexCoord).g / 255.0; + } + + roughness = min(0.99, roughness); gl_FragColor = metalRoughFunction(vec4(color, 1.0), - vertMetalness, + metalness, roughness, - vertAmbientOcclusion, + ambientOcclusion, vert, normalize(cameraPos - vert), - vertNormal); + normal); } diff --git a/src/ambientocclusionbaker.cpp b/src/ambientocclusionbaker.cpp index a431990c..1bfb6cff 100644 --- a/src/ambientocclusionbaker.cpp +++ b/src/ambientocclusionbaker.cpp @@ -143,6 +143,7 @@ void AmbientOcclusionBaker::process() m_resultMesh = new MeshLoader(m_meshResultContext); m_resultMesh->setTextureImage(new QImage(*m_textureImage)); + //m_resultMesh->setNormalMapImage(new QImage(*m_textureImage)); } this->moveToThread(QGuiApplication::instance()->thread()); diff --git a/src/gltffile.cpp b/src/gltffile.cpp index cffed23e..a25ab188 100644 --- a/src/gltffile.cpp +++ b/src/gltffile.cpp @@ -8,6 +8,7 @@ #include "version.h" #include "dust3dutil.h" #include "jointnodetree.h" +#include "meshloader.h" // Play with glTF online: // https://gltf-viewer.donmccurdy.com/ @@ -157,8 +158,8 @@ GltfFileWriter::GltfFileWriter(MeshResultContext &resultContext, m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["WEIGHTS_0"] = bufferViewIndex + (++attributeIndex); } m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorTexture"]["index"] = 0; - m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = part.second.material.metalness; - m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = part.second.material.roughness; + m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = MeshLoader::m_defaultMetalness; + m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = MeshLoader::m_defaultRoughness; primitiveIndex++; diff --git a/src/imageforever.cpp b/src/imageforever.cpp new file mode 100644 index 00000000..d58a885f --- /dev/null +++ b/src/imageforever.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include "imageforever.h" + +struct ImageForeverItem +{ + QImage *image; + QUuid id; +}; +static std::map g_foreverMap; +static std::map g_foreverCacheKeyToIdMap; +static QMutex g_mapMutex; + +const QImage *ImageForever::get(const QUuid &id) +{ + QMutexLocker locker(&g_mapMutex); + auto findResult = g_foreverMap.find(id); + if (findResult == g_foreverMap.end()) + return nullptr; + return findResult->second.image; +} + +QUuid ImageForever::add(const QImage *image, QUuid toId) +{ + QMutexLocker locker(&g_mapMutex); + if (nullptr == image) + return QUuid(); + auto key = image->cacheKey(); + auto findResult = g_foreverCacheKeyToIdMap.find(key); + if (findResult != g_foreverCacheKeyToIdMap.end()) { + return findResult->second; + } + QUuid newId = toId.isNull() ? QUuid::createUuid() : toId; + if (g_foreverMap.find(newId) != g_foreverMap.end()) + return newId; + QImage *newImage = new QImage(*image); + g_foreverMap[newId] = {newImage, newId}; + g_foreverCacheKeyToIdMap[newImage->cacheKey()] = newId; + return newId; +} diff --git a/src/imageforever.h b/src/imageforever.h new file mode 100644 index 00000000..dacf0d58 --- /dev/null +++ b/src/imageforever.h @@ -0,0 +1,13 @@ +#ifndef IMAGE_FOREVER_H +#define IMAGE_FOREVER_H +#include +#include + +class ImageForever +{ +public: + static const QImage *get(const QUuid &id); + static QUuid add(const QImage *image, QUuid toId=QUuid()); +}; + +#endif diff --git a/src/materialeditwidget.cpp b/src/materialeditwidget.cpp new file mode 100644 index 00000000..e76a0391 --- /dev/null +++ b/src/materialeditwidget.cpp @@ -0,0 +1,307 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "theme.h" +#include "materialeditwidget.h" +#include "floatnumberwidget.h" +#include "version.h" +#include "imageforever.h" +#include "dust3dutil.h" + +QPushButton *MaterialEditWidget::createMapButton() +{ + QPushButton *mapButton = new QPushButton; + mapButton->setFixedSize(Theme::partPreviewImageSize * 2, Theme::partPreviewImageSize * 2); + mapButton->setFlat(true); + mapButton->setAutoFillBackground(true); + updateMapButtonBackground(mapButton, nullptr); + return mapButton; +} + +QImage *MaterialEditWidget::pickImage() +{ + QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), + tr("Image Files (*.png *.jpg *.bmp)")).trimmed(); + if (fileName.isEmpty()) + return nullptr; + QImage *image = new QImage(); + if (!image->load(fileName)) + return nullptr; + return image; +} + +MaterialEditWidget::MaterialEditWidget(const SkeletonDocument *document, QWidget *parent) : + QDialog(parent), + m_document(document) +{ + m_layers.resize(1); + + m_previewWidget = new ModelWidget(this); + m_previewWidget->setMinimumSize(128, 128); + m_previewWidget->resize(512, 512); + m_previewWidget->move(-128, -128); + + QFont nameFont; + nameFont.setWeight(QFont::Light); + nameFont.setPixelSize(9); + nameFont.setBold(false); + + QGridLayout *mapLayout = new QGridLayout; + int row = 0; + int col = 0; + for (int i = 1; i < (int)TextureType::Count; i++) { + QVBoxLayout *textureManageLayout = new QVBoxLayout; + + SkeletonMaterialMap item; + item.forWhat = (TextureType)i; + m_layers[0].maps.push_back(item); + + QPushButton *imageButton = createMapButton(); + connect(imageButton, &QPushButton::clicked, [=]() { + QImage *image = pickImage(); + if (nullptr == image) + return; + m_layers[0].maps[(int)i - 1].imageId = ImageForever::add(image); + updateMapButtonBackground(imageButton, image); + delete image; + emit layersAdjusted(); + }); + + QLabel *nameLabel = new QLabel(TextureTypeToDispName(item.forWhat)); + nameLabel->setFont(nameFont); + + QPushButton *eraser = new QPushButton(QChar(fa::eraser)); + Theme::initAwesomeToolButton(eraser); + + connect(eraser, &QPushButton::clicked, [=]() { + m_layers[0].maps[(int)i - 1].imageId = QUuid(); + updateMapButtonBackground(imageButton, nullptr); + emit layersAdjusted(); + }); + + QHBoxLayout *textureTitleLayout = new QHBoxLayout; + textureTitleLayout->addWidget(eraser); + textureTitleLayout->addWidget(nameLabel); + textureTitleLayout->addStretch(); + + textureManageLayout->addWidget(imageButton); + textureManageLayout->addLayout(textureTitleLayout); + m_textureMapButtons[i - 1] = imageButton; + + mapLayout->addLayout(textureManageLayout, row, col++); + if (col == 2) { + col = 0; + row++; + } + } + + QVBoxLayout *rightLayout = new QVBoxLayout; + rightLayout->addStretch(); + rightLayout->addLayout(mapLayout); + rightLayout->addStretch(); + + QHBoxLayout *paramtersLayout = new QHBoxLayout; + paramtersLayout->setContentsMargins(256, 0, 0, 0); + paramtersLayout->addStretch(); + paramtersLayout->addLayout(rightLayout); + + m_nameEdit = new QLineEdit; + connect(m_nameEdit, &QLineEdit::textChanged, this, [=]() { + m_unsaved = true; + updateTitle(); + }); + QPushButton *saveButton = new QPushButton(tr("Save")); + connect(saveButton, &QPushButton::clicked, this, &MaterialEditWidget::save); + saveButton->setDefault(true); + + QHBoxLayout *baseInfoLayout = new QHBoxLayout; + baseInfoLayout->addWidget(new QLabel(tr("Name"))); + baseInfoLayout->addWidget(m_nameEdit); + baseInfoLayout->addStretch(); + baseInfoLayout->addWidget(saveButton); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(paramtersLayout); + mainLayout->addStretch(); + mainLayout->addWidget(Theme::createHorizontalLineWidget()); + mainLayout->addLayout(baseInfoLayout); + + setLayout(mainLayout); + + connect(this, &MaterialEditWidget::layersAdjusted, this, &MaterialEditWidget::updatePreview); + connect(this, &MaterialEditWidget::layersAdjusted, [=]() { + m_unsaved = true; + updateTitle(); + }); + connect(this, &MaterialEditWidget::addMaterial, document, &SkeletonDocument::addMaterial); + connect(this, &MaterialEditWidget::renameMaterial, document, &SkeletonDocument::renameMaterial); + connect(this, &MaterialEditWidget::setMaterialLayers, document, &SkeletonDocument::setMaterialLayers); + + updatePreview(); + updateTitle(); +} + +void MaterialEditWidget::updateMapButtonBackground(QPushButton *button, const QImage *image) +{ + QPalette palette; + if (nullptr != image) + palette.setBrush(button->backgroundRole(), QBrush(QPixmap::fromImage(*image))); + else + palette.setBrush(button->backgroundRole(), QBrush(Qt::black)); + button->setPalette(palette); +} + +void MaterialEditWidget::reject() +{ + close(); +} + +void MaterialEditWidget::closeEvent(QCloseEvent *event) +{ + if (m_unsaved && !m_closed) { + QMessageBox::StandardButton answer = QMessageBox::question(this, + APP_NAME, + tr("Do you really want to close while there are unsaved changes?"), + QMessageBox::Yes | QMessageBox::No); + if (answer != QMessageBox::Yes) { + event->ignore(); + return; + } + } + m_closed = true; + hide(); + if (nullptr != m_materialPreviewsGenerator) { + event->ignore(); + return; + } + event->accept(); +} + +QSize MaterialEditWidget::sizeHint() const +{ + return QSize(0, 200); +} + +MaterialEditWidget::~MaterialEditWidget() +{ + Q_ASSERT(nullptr == m_materialPreviewsGenerator); +} + +void MaterialEditWidget::updatePreview() +{ + if (m_closed) + return; + + if (nullptr != m_materialPreviewsGenerator) { + m_isPreviewDirty = true; + return; + } + + m_isPreviewDirty = false; + + qDebug() << "Material preview generating.."; + + QThread *thread = new QThread; + m_materialPreviewsGenerator = new MaterialPreviewsGenerator(); + m_materialPreviewsGenerator->addMaterial(QUuid(), m_layers); + m_materialPreviewsGenerator->moveToThread(thread); + connect(thread, &QThread::started, m_materialPreviewsGenerator, &MaterialPreviewsGenerator::process); + connect(m_materialPreviewsGenerator, &MaterialPreviewsGenerator::finished, this, &MaterialEditWidget::previewReady); + connect(m_materialPreviewsGenerator, &MaterialPreviewsGenerator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +void MaterialEditWidget::previewReady() +{ + m_previewWidget->updateMesh(m_materialPreviewsGenerator->takePreview(QUuid())); + + delete m_materialPreviewsGenerator; + m_materialPreviewsGenerator = nullptr; + + qDebug() << "Material preview generation done"; + + if (m_closed) { + close(); + return; + } + + if (m_isPreviewDirty) + updatePreview(); +} + +void MaterialEditWidget::setEditMaterialId(QUuid materialId) +{ + if (m_materialId == materialId) + return; + + m_materialId = materialId; + updateTitle(); +} + +void MaterialEditWidget::updateTitle() +{ + if (m_materialId.isNull()) { + setWindowTitle(unifiedWindowTitle(tr("New") + (m_unsaved ? "*" : ""))); + return; + } + const SkeletonMaterial *material = m_document->findMaterial(m_materialId); + if (nullptr == material) { + qDebug() << "Find material failed:" << m_materialId; + return; + } + setWindowTitle(unifiedWindowTitle(material->name + (m_unsaved ? "*" : ""))); +} + +void MaterialEditWidget::setEditMaterialName(QString name) +{ + m_nameEdit->setText(name); + updateTitle(); +} + +void MaterialEditWidget::setEditMaterialLayers(std::vector layers) +{ + for (int i = 1; i < (int)TextureType::Count; i++) { + m_layers[0].maps[i - 1].imageId = QUuid(); + } + if (!layers.empty()) { + for (const auto &layer: layers) { + for (const auto &mapItem: layer.maps) { + int index = (int)mapItem.forWhat - 1; + if (index >= 0 && index < (int)TextureType::Count - 1) { + m_layers[0].maps[index].imageId = mapItem.imageId; + } + } + } + } + for (int i = 1; i < (int)TextureType::Count; i++) { + updateMapButtonBackground(m_textureMapButtons[i - 1], ImageForever::get(m_layers[0].maps[i - 1].imageId)); + } + updatePreview(); +} + +void MaterialEditWidget::clearUnsaveState() +{ + m_unsaved = false; + updateTitle(); +} + +void MaterialEditWidget::save() +{ + if (m_materialId.isNull()) { + emit addMaterial(m_nameEdit->text(), m_layers); + } else if (m_unsaved) { + emit renameMaterial(m_materialId, m_nameEdit->text()); + emit setMaterialLayers(m_materialId, m_layers); + } + m_unsaved = false; + close(); +} diff --git a/src/materialeditwidget.h b/src/materialeditwidget.h new file mode 100644 index 00000000..5bf96435 --- /dev/null +++ b/src/materialeditwidget.h @@ -0,0 +1,58 @@ +#ifndef MATERIAL_EDIT_WIDGET_H +#define MATERIAL_EDIT_WIDGET_H +#include +#include +#include +#include +#include "skeletondocument.h" +#include "modelwidget.h" +#include "materialpreviewsgenerator.h" + +enum class PopupWidgetType +{ + PitchYawRoll, + Intersection +}; + +class MaterialEditWidget : public QDialog +{ + Q_OBJECT +signals: + void addMaterial(QString name, std::vector layers); + void removeMaterial(QUuid materialId); + void setMaterialLayers(QUuid materialId, std::vector layers); + void renameMaterial(QUuid materialId, QString name); + void layersAdjusted(); +public: + MaterialEditWidget(const SkeletonDocument *document, QWidget *parent=nullptr); + ~MaterialEditWidget(); +public slots: + void updatePreview(); + void setEditMaterialId(QUuid materialId); + void setEditMaterialName(QString name); + void setEditMaterialLayers(std::vector layers); + void updateTitle(); + void save(); + void clearUnsaveState(); + void previewReady(); +protected: + QSize sizeHint() const override; + void closeEvent(QCloseEvent *event) override; + void reject() override; +private: + void updateMapButtonBackground(QPushButton *button, const QImage *image); + QPushButton *createMapButton(); + QImage *pickImage(); + const SkeletonDocument *m_document = nullptr; + MaterialPreviewsGenerator *m_materialPreviewsGenerator = nullptr; + ModelWidget *m_previewWidget = nullptr; + bool m_isPreviewDirty = false; + bool m_closed = false; + QUuid m_materialId; + bool m_unsaved = false; + QLineEdit *m_nameEdit = nullptr; + std::vector m_layers; + QPushButton *m_textureMapButtons[(int)TextureType::Count - 1] = {nullptr}; +}; + +#endif diff --git a/src/materiallistwidget.cpp b/src/materiallistwidget.cpp new file mode 100644 index 00000000..bb6ef69f --- /dev/null +++ b/src/materiallistwidget.cpp @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include +#include "skeletonxml.h" +#include "materiallistwidget.h" + +MaterialListWidget::MaterialListWidget(const SkeletonDocument *document, QWidget *parent) : + QTreeWidget(parent), + m_document(document) +{ + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + setFocusPolicy(Qt::NoFocus); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setAutoScroll(false); + + setHeaderHidden(true); + + QPalette palette = this->palette(); + palette.setColor(QPalette::Window, Qt::transparent); + palette.setColor(QPalette::Base, Qt::transparent); + setPalette(palette); + + setStyleSheet("QTreeView {qproperty-indentation: 0;}"); + + setContentsMargins(0, 0, 0, 0); + + connect(document, &SkeletonDocument::materialListChanged, this, &MaterialListWidget::reload); + connect(document, &SkeletonDocument::cleanup, this, &MaterialListWidget::removeAllContent); + + connect(this, &MaterialListWidget::removeMaterial, document, &SkeletonDocument::removeMaterial); + + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &QTreeWidget::customContextMenuRequested, this, &MaterialListWidget::showContextMenu); + + reload(); +} + +void MaterialListWidget::enableMultipleSelection(bool enabled) +{ + m_multipleSelectionEnabled = enabled; +} + +void MaterialListWidget::materialRemoved(QUuid materialId) +{ + if (m_currentSelectedMaterialId == materialId) + m_currentSelectedMaterialId = QUuid(); + m_selectedMaterialIds.erase(materialId); + m_itemMap.erase(materialId); +} + +void MaterialListWidget::updateMaterialSelectState(QUuid materialId, bool selected) +{ + auto findItemResult = m_itemMap.find(materialId); + if (findItemResult == m_itemMap.end()) { + qDebug() << "Find material item failed:" << materialId; + return; + } + MaterialWidget *materialWidget = (MaterialWidget *)itemWidget(findItemResult->second.first, findItemResult->second.second); + materialWidget->updateCheckedState(selected); + if (m_cornerButtonVisible) { + materialWidget->setCornerButtonVisible(selected); + } +} + +void MaterialListWidget::selectMaterial(QUuid materialId, bool multiple) +{ + if (multiple) { + if (!m_currentSelectedMaterialId.isNull()) { + m_selectedMaterialIds.insert(m_currentSelectedMaterialId); + m_currentSelectedMaterialId = QUuid(); + } + if (m_selectedMaterialIds.find(materialId) != m_selectedMaterialIds.end()) { + updateMaterialSelectState(materialId, false); + m_selectedMaterialIds.erase(materialId); + } else { + if (m_multipleSelectionEnabled || m_selectedMaterialIds.empty()) { + updateMaterialSelectState(materialId, true); + m_selectedMaterialIds.insert(materialId); + } + } + if (m_selectedMaterialIds.size() > 1) { + return; + } + if (m_selectedMaterialIds.size() == 1) + materialId = *m_selectedMaterialIds.begin(); + else { + materialId = QUuid(); + emit currentSelectedMaterialChanged(materialId); + } + } + if (!m_selectedMaterialIds.empty()) { + for (const auto &id: m_selectedMaterialIds) { + updateMaterialSelectState(id, false); + } + m_selectedMaterialIds.clear(); + } + if (m_currentSelectedMaterialId != materialId) { + if (!m_currentSelectedMaterialId.isNull()) { + updateMaterialSelectState(m_currentSelectedMaterialId, false); + } + m_currentSelectedMaterialId = materialId; + if (!m_currentSelectedMaterialId.isNull()) { + updateMaterialSelectState(m_currentSelectedMaterialId, true); + } + emit currentSelectedMaterialChanged(m_currentSelectedMaterialId); + } +} + +void MaterialListWidget::mousePressEvent(QMouseEvent *event) +{ + QModelIndex itemIndex = indexAt(event->pos()); + QTreeView::mousePressEvent(event); + if (event->button() == Qt::LeftButton) { + bool multiple = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier); + if (itemIndex.isValid()) { + QTreeWidgetItem *item = itemFromIndex(itemIndex); + auto materialId = QUuid(item->data(itemIndex.column(), Qt::UserRole).toString()); + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { + bool startAdd = false; + bool stopAdd = false; + std::vector waitQueue; + for (const auto &childId: m_document->materialIdList) { + if (m_shiftStartMaterialId == childId || materialId == childId) { + if (startAdd) { + stopAdd = true; + } else { + startAdd = true; + } + } + if (startAdd) + waitQueue.push_back(childId); + if (stopAdd) + break; + } + if (stopAdd && !waitQueue.empty()) { + if (!m_selectedMaterialIds.empty()) { + for (const auto &id: m_selectedMaterialIds) { + updateMaterialSelectState(id, false); + } + m_selectedMaterialIds.clear(); + } + if (!m_currentSelectedMaterialId.isNull()) { + m_currentSelectedMaterialId = QUuid(); + } + for (const auto &waitId: waitQueue) { + selectMaterial(waitId, true); + } + } + return; + } else { + m_shiftStartMaterialId = materialId; + } + selectMaterial(materialId, multiple); + return; + } + if (!multiple) + selectMaterial(QUuid()); + } +} + +bool MaterialListWidget::isMaterialSelected(QUuid materialId) +{ + return (m_currentSelectedMaterialId == materialId || + m_selectedMaterialIds.find(materialId) != m_selectedMaterialIds.end()); +} + +void MaterialListWidget::showContextMenu(const QPoint &pos) +{ + if (!m_hasContextMenu) + return; + + QMenu contextMenu(this); + + std::set unorderedMaterialIds = m_selectedMaterialIds; + if (!m_currentSelectedMaterialId.isNull()) + unorderedMaterialIds.insert(m_currentSelectedMaterialId); + + std::vector materialIds; + for (const auto &cand: m_document->materialIdList) { + if (unorderedMaterialIds.find(cand) != unorderedMaterialIds.end()) + materialIds.push_back(cand); + } + + QAction modifyAction(tr("Modify"), this); + if (materialIds.size() == 1) { + connect(&modifyAction, &QAction::triggered, this, [=]() { + emit modifyMaterial(*materialIds.begin()); + }); + contextMenu.addAction(&modifyAction); + } + + QAction copyAction(tr("Copy"), this); + if (!materialIds.empty()) { + connect(©Action, &QAction::triggered, this, &MaterialListWidget::copy); + contextMenu.addAction(©Action); + } + + QAction pasteAction(tr("Paste"), this); + if (m_document->hasPastableMaterialsInClipboard()) { + connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste); + contextMenu.addAction(&pasteAction); + } + + QAction deleteAction(tr("Delete"), this); + if (!materialIds.empty()) { + connect(&deleteAction, &QAction::triggered, [=]() { + for (const auto &materialId: materialIds) + emit removeMaterial(materialId); + }); + contextMenu.addAction(&deleteAction); + } + + contextMenu.exec(mapToGlobal(pos)); +} + +void MaterialListWidget::resizeEvent(QResizeEvent *event) +{ + QTreeWidget::resizeEvent(event); + if (calculateColumnCount() != columnCount()) + reload(); +} + +int MaterialListWidget::calculateColumnCount() +{ + if (nullptr == parentWidget()) + return 0; + + int columns = parentWidget()->width() / Theme::materialPreviewImageSize; + if (0 == columns) + columns = 1; + return columns; +} + +void MaterialListWidget::reload() +{ + removeAllContent(); + + int columns = calculateColumnCount(); + if (0 == columns) + return; + + int columnWidth = parentWidget()->width() / columns; + + //qDebug() << "parentWidth:" << parentWidget()->width() << "columnWidth:" << columnWidth << "columns:" << columns; + + setColumnCount(columns); + for (int i = 0; i < columns; i++) + setColumnWidth(i, columnWidth); + + decltype(m_document->materialIdList.size()) materialIndex = 0; + while (materialIndex < m_document->materialIdList.size()) { + QTreeWidgetItem *item = new QTreeWidgetItem(this); + item->setFlags((item->flags() | Qt::ItemIsEnabled) & ~(Qt::ItemIsSelectable) & ~(Qt::ItemIsEditable)); + for (int col = 0; col < columns && materialIndex < m_document->materialIdList.size(); col++, materialIndex++) { + const auto &materialId = m_document->materialIdList[materialIndex]; + item->setSizeHint(col, QSize(columnWidth, MaterialWidget::preferredHeight() + 2)); + item->setData(col, Qt::UserRole, materialId.toString()); + MaterialWidget *widget = new MaterialWidget(m_document, materialId); + connect(widget, &MaterialWidget::modifyMaterial, this, &MaterialListWidget::modifyMaterial); + connect(widget, &MaterialWidget::cornerButtonClicked, this, &MaterialListWidget::cornerButtonClicked); + widget->previewWidget()->setGraphicsFunctions(this); + setItemWidget(item, col, widget); + widget->reload(); + widget->updateCheckedState(isMaterialSelected(materialId)); + m_itemMap[materialId] = std::make_pair(item, col); + } + invisibleRootItem()->addChild(item); + } +} + +void MaterialListWidget::setCornerButtonVisible(bool visible) +{ + m_cornerButtonVisible = visible; +} + +void MaterialListWidget::setHasContextMenu(bool hasContextMenu) +{ + m_hasContextMenu = hasContextMenu; +} + +void MaterialListWidget::removeAllContent() +{ + m_itemMap.clear(); + clear(); +} + +bool MaterialListWidget::mouseMove(QMouseEvent *event) +{ + return false; +} + +bool MaterialListWidget::wheel(QWheelEvent *event) +{ + return false; +} + +bool MaterialListWidget::mouseRelease(QMouseEvent *event) +{ + return false; +} + +bool MaterialListWidget::mousePress(QMouseEvent *event) +{ + if (event->button() == Qt::RightButton) { + showContextMenu(mapFromGlobal(event->globalPos())); + return false; + } + return false; +} + +bool MaterialListWidget::mouseDoubleClick(QMouseEvent *event) +{ + return false; +} + +bool MaterialListWidget::keyPress(QKeyEvent *event) +{ + return false; +} + +void MaterialListWidget::copy() +{ + if (m_selectedMaterialIds.empty() && m_currentSelectedMaterialId.isNull()) + return; + + std::set limitMaterialIds = m_selectedMaterialIds; + if (!m_currentSelectedMaterialId.isNull()) + limitMaterialIds.insert(m_currentSelectedMaterialId); + + std::set emptySet; + + SkeletonSnapshot snapshot; + m_document->toSnapshot(&snapshot, emptySet, SkeletonDocumentToSnapshotFor::Materials, + emptySet, emptySet, limitMaterialIds); + QString snapshotXml; + QXmlStreamWriter xmlStreamWriter(&snapshotXml); + saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(snapshotXml); +} diff --git a/src/materiallistwidget.h b/src/materiallistwidget.h new file mode 100644 index 00000000..b4961b24 --- /dev/null +++ b/src/materiallistwidget.h @@ -0,0 +1,52 @@ +#ifndef MATERIAL_LIST_WIDGET_H +#define MATERIAL_LIST_WIDGET_H +#include +#include +#include "skeletondocument.h" +#include "materialwidget.h" +#include "skeletongraphicswidget.h" + +class MaterialListWidget : public QTreeWidget, public SkeletonGraphicsFunctions +{ + Q_OBJECT +signals: + void removeMaterial(QUuid materialId); + void modifyMaterial(QUuid materialId); + void cornerButtonClicked(QUuid materialId); + void currentSelectedMaterialChanged(QUuid materialId); +public: + MaterialListWidget(const SkeletonDocument *document, QWidget *parent=nullptr); + bool isMaterialSelected(QUuid materialId); + void enableMultipleSelection(bool enabled); +public slots: + void reload(); + void removeAllContent(); + void materialRemoved(QUuid materialId); + void showContextMenu(const QPoint &pos); + void selectMaterial(QUuid materialId, bool multiple=false); + void copy(); + void setCornerButtonVisible(bool visible); + void setHasContextMenu(bool hasContextMenu); +protected: + void resizeEvent(QResizeEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + bool mouseMove(QMouseEvent *event) override; + bool wheel(QWheelEvent *event) override; + bool mouseRelease(QMouseEvent *event) override; + bool mousePress(QMouseEvent *event) override; + bool mouseDoubleClick(QMouseEvent *event) override; + bool keyPress(QKeyEvent *event) override; +private: + int calculateColumnCount(); + void updateMaterialSelectState(QUuid materialId, bool selected); + const SkeletonDocument *m_document = nullptr; + std::map> m_itemMap; + std::set m_selectedMaterialIds; + QUuid m_currentSelectedMaterialId; + QUuid m_shiftStartMaterialId; + bool m_cornerButtonVisible = false; + bool m_hasContextMenu = true; + bool m_multipleSelectionEnabled = true; +}; + +#endif diff --git a/src/materialmanagewidget.cpp b/src/materialmanagewidget.cpp new file mode 100644 index 00000000..edef81e6 --- /dev/null +++ b/src/materialmanagewidget.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include "materialmanagewidget.h" +#include "theme.h" +#include "materialeditwidget.h" +#include "infolabel.h" + +MaterialManageWidget::MaterialManageWidget(const SkeletonDocument *document, QWidget *parent) : + QWidget(parent), + m_document(document) +{ + QPushButton *addMaterialButton = new QPushButton(Theme::awesome()->icon(fa::plus), tr("Add Material...")); + + connect(addMaterialButton, &QPushButton::clicked, this, &MaterialManageWidget::showAddMaterialDialog); + + QHBoxLayout *toolsLayout = new QHBoxLayout; + toolsLayout->addWidget(addMaterialButton); + + m_materialListWidget = new MaterialListWidget(document); + connect(m_materialListWidget, &MaterialListWidget::modifyMaterial, this, &MaterialManageWidget::showMaterialDialog); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(toolsLayout); + mainLayout->addWidget(m_materialListWidget); + + setLayout(mainLayout); +} + +MaterialListWidget *MaterialManageWidget::materialListWidget() +{ + return m_materialListWidget; +} + +QSize MaterialManageWidget::sizeHint() const +{ + return QSize(Theme::sidebarPreferredWidth, 0); +} + +void MaterialManageWidget::showAddMaterialDialog() +{ + showMaterialDialog(QUuid()); +} + +void MaterialManageWidget::showMaterialDialog(QUuid materialId) +{ + MaterialEditWidget *materialEditWidget = new MaterialEditWidget(m_document); + materialEditWidget->setAttribute(Qt::WA_DeleteOnClose); + if (!materialId.isNull()) { + const SkeletonMaterial *material = m_document->findMaterial(materialId); + if (nullptr != material) { + materialEditWidget->setEditMaterialId(materialId); + materialEditWidget->setEditMaterialName(material->name); + materialEditWidget->setEditMaterialLayers(material->layers); + materialEditWidget->clearUnsaveState(); + } + } + materialEditWidget->show(); + connect(materialEditWidget, &QDialog::destroyed, [=]() { + emit unregisterDialog((QWidget *)materialEditWidget); + }); + emit registerDialog((QWidget *)materialEditWidget); +} diff --git a/src/materialmanagewidget.h b/src/materialmanagewidget.h new file mode 100644 index 00000000..3899ba2a --- /dev/null +++ b/src/materialmanagewidget.h @@ -0,0 +1,26 @@ +#ifndef MATERIAL_MANAGE_WIDGET_H +#define MATERIAL_MANAGE_WIDGET_H +#include +#include "skeletondocument.h" +#include "materiallistwidget.h" + +class MaterialManageWidget : public QWidget +{ + Q_OBJECT +signals: + void registerDialog(QWidget *widget); + void unregisterDialog(QWidget *widget); +public: + MaterialManageWidget(const SkeletonDocument *document, QWidget *parent=nullptr); + MaterialListWidget *materialListWidget(); +protected: + virtual QSize sizeHint() const; +public slots: + void showAddMaterialDialog(); + void showMaterialDialog(QUuid materialId); +private: + const SkeletonDocument *m_document = nullptr; + MaterialListWidget *m_materialListWidget = nullptr; +}; + +#endif diff --git a/src/materialpreviewsgenerator.cpp b/src/materialpreviewsgenerator.cpp new file mode 100644 index 00000000..f9ff4ed5 --- /dev/null +++ b/src/materialpreviewsgenerator.cpp @@ -0,0 +1,122 @@ +#include +#include +#include "materialpreviewsgenerator.h" +#include "meshgenerator.h" +#include "skeletonxml.h" +#include "ds3file.h" +#include "texturegenerator.h" +#include "imageforever.h" + +MaterialPreviewsGenerator::MaterialPreviewsGenerator() +{ +} + +MaterialPreviewsGenerator::~MaterialPreviewsGenerator() +{ + for (auto &item: m_previews) { + delete item.second; + } +} + +void MaterialPreviewsGenerator::addMaterial(QUuid materialId, const std::vector &layers) +{ + m_materials.push_back({materialId, layers}); +} + +const std::set &MaterialPreviewsGenerator::generatedPreviewMaterialIds() +{ + return m_generatedMaterialIds; +} + +MeshLoader *MaterialPreviewsGenerator::takePreview(QUuid materialId) +{ + MeshLoader *resultMesh = m_previews[materialId]; + m_previews[materialId] = nullptr; + return resultMesh; +} + +void MaterialPreviewsGenerator::generate() +{ + SkeletonSnapshot *snapshot = new SkeletonSnapshot; + + std::vector partIds; + Ds3FileReader ds3Reader(":/resources/material-demo-model.ds3"); + for (int i = 0; i < ds3Reader.items().size(); ++i) { + Ds3ReaderItem item = ds3Reader.items().at(i); + if (item.type == "model") { + QByteArray data; + ds3Reader.loadItem(item.name, &data); + QXmlStreamReader stream(data); + loadSkeletonFromXmlStream(snapshot, stream); + for (const auto &item: snapshot->parts) { + partIds.push_back(QUuid(item.first)); + } + } + } + + GeneratedCacheContext *cacheContext = new GeneratedCacheContext(); + MeshGenerator *meshGenerator = new MeshGenerator(snapshot); + meshGenerator->setSmoothNormal(true); + meshGenerator->setWeldEnabled(false); + meshGenerator->setGeneratedCacheContext(cacheContext); + + meshGenerator->generate(); + for (const auto &mirror: cacheContext->partMirrorIdMap) { + partIds.push_back(QUuid(mirror.first)); + } + + MeshResultContext *meshResultContext = meshGenerator->takeMeshResultContext(); + + MeshLoader *resultMesh = meshGenerator->takeResultMesh(); + + for (const auto &material: m_materials) { + TextureGenerator *textureGenerator = new TextureGenerator(*meshResultContext); + for (const auto &layer: material.second) { + for (const auto &mapItem: layer.maps) { + const QImage *image = ImageForever::get(mapItem.imageId); + if (nullptr == image) + continue; + for (const auto &partId: partIds) { + if (TextureType::BaseColor == mapItem.forWhat) + textureGenerator->addPartColorMap(partId, image); + else if (TextureType::Normal == mapItem.forWhat) + textureGenerator->addPartNormalMap(partId, image); + else if (TextureType::Metalness == mapItem.forWhat) + textureGenerator->addPartMetalnessMap(partId, image); + else if (TextureType::Roughness == mapItem.forWhat) + textureGenerator->addPartRoughnessMap(partId, image); + else if (TextureType::AmbientOcclusion == mapItem.forWhat) + textureGenerator->addPartAmbientOcclusionMap(partId, image); + } + } + } + textureGenerator->generate(); + MeshLoader *texturedResultMesh = textureGenerator->takeResultMesh(); + if (nullptr != texturedResultMesh) { + m_previews[material.first] = new MeshLoader(*texturedResultMesh); + m_generatedMaterialIds.insert(material.first); + delete texturedResultMesh; + } + delete textureGenerator; + } + + delete resultMesh; + + delete meshResultContext; + + delete meshGenerator; + delete cacheContext; +} + +void MaterialPreviewsGenerator::process() +{ + QElapsedTimer countTimeConsumed; + countTimeConsumed.start(); + + generate(); + + qDebug() << "The material previews generation took" << countTimeConsumed.elapsed() << "milliseconds"; + + this->moveToThread(QGuiApplication::instance()->thread()); + emit finished(); +} diff --git a/src/materialpreviewsgenerator.h b/src/materialpreviewsgenerator.h new file mode 100644 index 00000000..037d8941 --- /dev/null +++ b/src/materialpreviewsgenerator.h @@ -0,0 +1,30 @@ +#ifndef MATERIAL_PREVIEWS_GENERATOR_H +#define MATERIAL_PREVIEWS_GENERATOR_H +#include +#include +#include +#include +#include "meshloader.h" +#include "skeletondocument.h" + +class MaterialPreviewsGenerator : public QObject +{ + Q_OBJECT +public: + MaterialPreviewsGenerator(); + ~MaterialPreviewsGenerator(); + void addMaterial(QUuid materialId, const std::vector &layers); + const std::set &generatedPreviewMaterialIds(); + MeshLoader *takePreview(QUuid materialId); + void generate(); +signals: + void finished(); +public slots: + void process(); +private: + std::vector>> m_materials; + std::map m_previews; + std::set m_generatedMaterialIds; +}; + +#endif diff --git a/src/materialwidget.cpp b/src/materialwidget.cpp new file mode 100644 index 00000000..95e9d212 --- /dev/null +++ b/src/materialwidget.cpp @@ -0,0 +1,107 @@ +#include +#include "materialwidget.h" + +MaterialWidget::MaterialWidget(const SkeletonDocument *document, QUuid materialId) : + m_materialId(materialId), + m_document(document) +{ + setObjectName("MaterialFrame"); + + m_previewWidget = new ModelWidget(this); + m_previewWidget->setFixedSize(Theme::materialPreviewImageSize, Theme::materialPreviewImageSize); + m_previewWidget->enableMove(false); + m_previewWidget->enableZoom(false); + + m_nameLabel = new QLabel; + m_nameLabel->setAlignment(Qt::AlignCenter); + m_nameLabel->setStyleSheet("background: qlineargradient(x1:0.5 y1:-15.5, x2:0.5 y2:1, stop:0 " + Theme::white.name() + ", stop:1 #252525);"); + + QFont nameFont; + nameFont.setWeight(QFont::Light); + nameFont.setPixelSize(9); + nameFont.setBold(false); + m_nameLabel->setFont(nameFont); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->addStretch(); + mainLayout->addWidget(m_nameLabel); + + setLayout(mainLayout); + + setFixedSize(Theme::materialPreviewImageSize, MaterialWidget::preferredHeight()); + + connect(document, &SkeletonDocument::materialNameChanged, this, &MaterialWidget::updateName); + connect(document, &SkeletonDocument::materialPreviewChanged, this, &MaterialWidget::updatePreview); +} + +void MaterialWidget::setCornerButtonVisible(bool visible) +{ + if (nullptr == m_cornerButton) { + m_cornerButton = new QPushButton(this); + m_cornerButton->move(Theme::materialPreviewImageSize - Theme::miniIconSize - 2, 2); + Theme::initAwesomeMiniButton(m_cornerButton); + m_cornerButton->setText(QChar(fa::plussquare)); + connect(m_cornerButton, &QPushButton::clicked, this, [=]() { + emit cornerButtonClicked(m_materialId); + }); + } + m_cornerButton->setVisible(visible); +} + +void MaterialWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + m_previewWidget->move((width() - Theme::materialPreviewImageSize) / 2, 0); +} + +int MaterialWidget::preferredHeight() +{ + return Theme::materialPreviewImageSize; +} + +void MaterialWidget::reload() +{ + updatePreview(); + updateName(); +} + +void MaterialWidget::updatePreview() +{ + const SkeletonMaterial *material = m_document->findMaterial(m_materialId); + if (!material) { + qDebug() << "Material not found:" << m_materialId; + return; + } + MeshLoader *previewMesh = material->takePreviewMesh(); + m_previewWidget->updateMesh(previewMesh); +} + +void MaterialWidget::updateName() +{ + const SkeletonMaterial *material = m_document->findMaterial(m_materialId); + if (!material) { + qDebug() << "Material not found:" << m_materialId; + return; + } + m_nameLabel->setText(material->name); +} + +void MaterialWidget::updateCheckedState(bool checked) +{ + if (checked) + setStyleSheet("#MaterialFrame {border: 1px solid " + Theme::red.name() + ";}"); + else + setStyleSheet("#MaterialFrame {border: 1px solid transparent;}"); +} + +ModelWidget *MaterialWidget::previewWidget() +{ + return m_previewWidget; +} + +void MaterialWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + QFrame::mouseDoubleClickEvent(event); + emit modifyMaterial(m_materialId); +} diff --git a/src/materialwidget.h b/src/materialwidget.h new file mode 100644 index 00000000..85dbe0f6 --- /dev/null +++ b/src/materialwidget.h @@ -0,0 +1,36 @@ +#ifndef MATERIAL_WIDGET_H +#define MATERIAL_WIDGET_H +#include +#include +#include +#include "skeletondocument.h" +#include "modelwidget.h" + +class MaterialWidget : public QFrame +{ + Q_OBJECT +signals: + void modifyMaterial(QUuid materialId); + void cornerButtonClicked(QUuid materialId); +public: + MaterialWidget(const SkeletonDocument *document, QUuid materialId); + static int preferredHeight(); + ModelWidget *previewWidget(); +protected: + void mouseDoubleClickEvent(QMouseEvent *event) override; + void resizeEvent(QResizeEvent *event) override; +public slots: + void reload(); + void updatePreview(); + void updateName(); + void updateCheckedState(bool checked); + void setCornerButtonVisible(bool visible); +private: + QUuid m_materialId; + const SkeletonDocument *m_document = nullptr; + ModelWidget *m_previewWidget = nullptr; + QLabel *m_nameLabel = nullptr; + QPushButton *m_cornerButton = nullptr; +}; + +#endif diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index b0a5c051..b968c0db 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -11,6 +11,7 @@ #include "positionmap.h" #include "meshquadify.h" #include "meshweldseam.h" +#include "imageforever.h" bool MeshGenerator::m_enableDebug = false; PositionMap *MeshGenerator::m_forMakePositionKey = new PositionMap; @@ -30,10 +31,9 @@ void GeneratedCacheContext::updateComponentCombinableMesh(QString componentId, v cache = cloneCombinableMesh(mesh); } -MeshGenerator::MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread) : +MeshGenerator::MeshGenerator(SkeletonSnapshot *snapshot) : m_snapshot(snapshot), m_mesh(nullptr), - m_thread(thread), m_meshResultContext(nullptr), m_sharedContextWidget(nullptr), m_cacheContext(nullptr), @@ -251,15 +251,33 @@ void *MeshGenerator::combinePartMesh(QString partId) if (MeshGenerator::m_enableDebug) meshlite_bmesh_enable_debug(m_meshliteContext, bmeshId, 1); - float metalness = 0.0; - QString metalnessString = valueOfKeyInMapOrEmpty(part, "metalness"); - if (!metalnessString.isEmpty()) - metalness = metalnessString.toFloat(); + QUuid materialId; + QString materialIdString = valueOfKeyInMapOrEmpty(part, "materialId"); + if (!materialIdString.isEmpty()) + materialId = QUuid(materialIdString); - float roughness = 1.0; - QString roughnessString = valueOfKeyInMapOrEmpty(part, "roughness"); - if (!roughnessString.isEmpty()) - roughness = roughnessString.toFloat(); + Material partMaterial; + for (const auto &material: m_snapshot->materials) { + if (materialIdString != valueOfKeyInMapOrEmpty(material.first, "id")) + continue; + for (const auto &layer: material.second) { + //FIXME: Only support one layer currently + for (const auto &mapItem: layer.second) { + auto textureType = TextureTypeFromString(valueOfKeyInMapOrEmpty(mapItem, "for").toUtf8().constData()); + if (textureType != TextureType::None) { + int index = (int)textureType - 1; + if (index >= 0 && index < (int)TextureType::Count - 1) { + if ("imageId" == valueOfKeyInMapOrEmpty(mapItem, "linkDataType")) { + auto imageIdString = valueOfKeyInMapOrEmpty(mapItem, "linkData"); + partMaterial.textureImages[index] = ImageForever::get(QUuid(imageIdString)); + } + } + } + } + break; + } + break; + } QString mirroredPartId; QUuid mirroredPartIdNotAsString; @@ -301,9 +319,8 @@ void *MeshGenerator::combinePartMesh(QString partId) bmeshNode.origin = QVector3D(x, y, z); bmeshNode.radius = radius; bmeshNode.nodeId = QUuid(nodeId); + bmeshNode.material = partMaterial; bmeshNode.material.color = partColor; - bmeshNode.material.metalness = metalness; - bmeshNode.material.roughness = roughness; bmeshNode.boneMark = boneMark; //if (SkeletonBoneMark::None != boneMark) // bmeshNode.color = SkeletonBoneMarkToColor(boneMark); @@ -374,7 +391,7 @@ void *MeshGenerator::combinePartMesh(QString partId) if (m_requirePreviewPartIds.find(partIdNotAsString) != m_requirePreviewPartIds.end()) { int trimedMeshId = meshlite_trim(m_meshliteContext, meshId, 1); - m_partPreviewMeshMap[partIdNotAsString] = new MeshLoader(m_meshliteContext, trimedMeshId, -1, {partColor, metalness, roughness}, nullptr, m_smoothNormal); + m_partPreviewMeshMap[partIdNotAsString] = new MeshLoader(m_meshliteContext, trimedMeshId, -1, partColor, nullptr, m_smoothNormal); m_generatedPreviewPartIds.insert(partIdNotAsString); } @@ -581,7 +598,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse) return resultMesh; } -void MeshGenerator::process() +void MeshGenerator::generate() { if (nullptr == m_snapshot) return; @@ -714,7 +731,7 @@ void MeshGenerator::process() if (resultMeshId > 0) { loadGeneratedPositionsToMeshResultContext(m_meshliteContext, triangulatedFinalMeshId); - m_mesh = new MeshLoader(m_meshliteContext, resultMeshId, triangulatedFinalMeshId, {Theme::white, 0.0, 1.0}, &m_meshResultContext->triangleMaterials(), m_smoothNormal); + m_mesh = new MeshLoader(m_meshliteContext, resultMeshId, triangulatedFinalMeshId, Theme::white, &m_meshResultContext->triangleMaterials(), m_smoothNormal); } if (needDeleteCacheContext) { @@ -725,6 +742,11 @@ void MeshGenerator::process() meshlite_destroy_context(m_meshliteContext); qDebug() << "The mesh generation took" << countTimeConsumed.elapsed() << "milliseconds"; +} + +void MeshGenerator::process() +{ + generate(); this->moveToThread(QGuiApplication::instance()->thread()); emit finished(); diff --git a/src/meshgenerator.h b/src/meshgenerator.h index 8b9e3d7a..8e7e5d58 100644 --- a/src/meshgenerator.h +++ b/src/meshgenerator.h @@ -30,7 +30,7 @@ class MeshGenerator : public QObject { Q_OBJECT public: - MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread); + MeshGenerator(SkeletonSnapshot *snapshot); ~MeshGenerator(); void setSharedContextWidget(QOpenGLWidget *widget); void addPartPreviewRequirement(const QUuid &partId); @@ -42,6 +42,7 @@ public: const std::set &requirePreviewPartIds(); const std::set &generatedPreviewPartIds(); MeshResultContext *takeMeshResultContext(); + void generate(); signals: void finished(); public slots: diff --git a/src/meshloader.cpp b/src/meshloader.cpp index 95c9ee2a..7779dc7f 100644 --- a/src/meshloader.cpp +++ b/src/meshloader.cpp @@ -1,12 +1,18 @@ #include +#include +#include #include "meshloader.h" #include "meshlite.h" #include "theme.h" #include "positionmap.h" +#include "ds3file.h" #define MAX_VERTICES_PER_FACE 100 -MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Material material, const std::vector *triangleMaterials, bool smoothNormal) : +float MeshLoader::m_defaultMetalness = 0.0; +float MeshLoader::m_defaultRoughness = 1.0; + +MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, QColor defaultColor, const std::vector *triangleMaterials, bool smoothNormal) : m_triangleVertices(nullptr), m_triangleVertexCount(0), m_edgeVertices(nullptr), @@ -97,9 +103,9 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Mater GLfloat *triangleNormals = new GLfloat[triangleCount * 3]; int loadedTriangleNormalItemCount = meshlite_get_triangle_normal_array(meshlite, triangleMesh, triangleNormals, triangleCount * 3); - float modelR = material.color.redF(); - float modelG = material.color.greenF(); - float modelB = material.color.blueF(); + float modelR = defaultColor.redF(); + float modelG = defaultColor.greenF(); + float modelB = defaultColor.blueF(); m_triangleVertexCount = triangleCount * 3; m_triangleVertices = new Vertex[m_triangleVertexCount * 3]; for (int i = 0; i < triangleCount; i++) { @@ -107,15 +113,13 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Mater float useColorR = modelR; float useColorG = modelG; float useColorB = modelB; - float useMetalness = material.metalness; - float useRoughness = material.roughness; + float useMetalness = m_defaultMetalness; + float useRoughness = m_defaultRoughness; if (triangleMaterials && i < (int)triangleMaterials->size()) { auto triangleMaterial = (*triangleMaterials)[i]; useColorR = triangleMaterial.color.redF(); useColorG = triangleMaterial.color.greenF(); useColorB = triangleMaterial.color.blueF(); - useMetalness = triangleMaterial.metalness; - useRoughness = triangleMaterial.roughness; } TriangulatedFace triangulatedFace; triangulatedFace.color.setRedF(useColorR); @@ -200,6 +204,9 @@ MeshLoader::MeshLoader(const MeshLoader &mesh) : if (nullptr != mesh.m_textureImage) { this->m_textureImage = new QImage(*mesh.m_textureImage); } + if (nullptr != mesh.m_normalMapImage) { + this->m_normalMapImage = new QImage(*mesh.m_normalMapImage); + } this->m_vertices = mesh.m_vertices; this->m_faces = mesh.m_faces; this->m_triangulatedVertices = mesh.m_triangulatedVertices; @@ -228,13 +235,15 @@ MeshLoader::MeshLoader(MeshResultContext &resultContext) : m_triangleVertices = new Vertex[m_triangleVertexCount]; int destIndex = 0; for (const auto &part: resultContext.parts()) { - for (const auto &it: part.second.triangles) { + for (int x = 0; x < (int)part.second.triangles.size(); x++) { + const auto &it = part.second.triangles[x]; for (auto i = 0; i < 3; i++) { int vertexIndex = it.indicies[i]; const ResultVertex *srcVert = &part.second.vertices[vertexIndex]; const QVector3D *srcNormal = &part.second.interpolatedVertexNormals[vertexIndex]; const ResultVertexUv *srcUv = &part.second.vertexUvs[vertexIndex]; - const Material *srcMaterial = &part.second.material; + //const Material *srcMaterial = &part.second.material; + const QVector3D *srcTangent = &part.second.triangleTangents[x]; Vertex *dest = &m_triangleVertices[destIndex]; dest->colorR = 0; dest->colorG = 0; @@ -247,8 +256,11 @@ MeshLoader::MeshLoader(MeshResultContext &resultContext) : dest->normX = srcNormal->x(); dest->normY = srcNormal->y(); dest->normZ = srcNormal->z(); - dest->metalness = srcMaterial->metalness; - dest->roughness = srcMaterial->roughness; + dest->metalness = m_defaultMetalness; + dest->roughness = m_defaultRoughness; + dest->tangentX = srcTangent->x(); + dest->tangentY = srcTangent->y(); + dest->tangentZ = srcTangent->z(); destIndex++; } } @@ -271,6 +283,7 @@ MeshLoader::~MeshLoader() delete[] m_edgeVertices; m_edgeVertexCount = 0; delete m_textureImage; + delete m_normalMapImage; } const std::vector &MeshLoader::vertices() @@ -322,3 +335,72 @@ const QImage *MeshLoader::textureImage() { return m_textureImage; } + +void MeshLoader::setNormalMapImage(QImage *normalMapImage) +{ + m_normalMapImage = normalMapImage; +} + +const QImage *MeshLoader::normalMapImage() +{ + return m_normalMapImage; +} + +const QImage *MeshLoader::metalnessRoughnessAmbientOcclusionImage() +{ + return m_metalnessRoughnessAmbientOcclusionImage; +} + +void MeshLoader::setMetalnessRoughnessAmbientOcclusionImage(QImage *image) +{ + m_metalnessRoughnessAmbientOcclusionImage = image; +} + +bool MeshLoader::hasMetalnessInImage() +{ + return m_hasMetalnessInImage; +} + +void MeshLoader::setHasMetalnessInImage(bool hasInImage) +{ + m_hasMetalnessInImage = hasInImage; +} + +bool MeshLoader::hasRoughnessInImage() +{ + return m_hasRoughnessInImage; +} + +void MeshLoader::setHasRoughnessInImage(bool hasInImage) +{ + m_hasRoughnessInImage = hasInImage; +} + +bool MeshLoader::hasAmbientOcclusionInImage() +{ + return m_hasAmbientOcclusionInImage; +} + +void MeshLoader::setHasAmbientOcclusionInImage(bool hasInImage) +{ + m_hasAmbientOcclusionInImage = hasInImage; +} + +void MeshLoader::exportAsObj(const QString &filename) +{ + QFile file(filename); + if (file.open(QIODevice::WriteOnly)) { + QTextStream stream(&file); + stream << "# " << Ds3FileReader::m_applicationName << endl; + for (std::vector::const_iterator it = vertices().begin() ; it != vertices().end(); ++it) { + stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl; + } + for (std::vector>::const_iterator it = faces().begin() ; it != faces().end(); ++it) { + stream << "f"; + for (std::vector::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) { + stream << " " << (1 + *subIt); + } + stream << endl; + } + } +} diff --git a/src/meshloader.h b/src/meshloader.h index ed364ceb..2aac5d95 100644 --- a/src/meshloader.h +++ b/src/meshloader.h @@ -28,6 +28,9 @@ typedef struct GLfloat texV; GLfloat metalness; GLfloat roughness; + GLfloat tangentX; + GLfloat tangentY; + GLfloat tangentZ; } Vertex; #pragma pack(pop) @@ -40,7 +43,7 @@ struct TriangulatedFace class MeshLoader { public: - MeshLoader(void *meshlite, int meshId, int triangulatedMeshId=-1, Material material={Theme::white, 0.0, 1.0}, const std::vector *triangleMaterials=nullptr, bool smoothNormal=true); + MeshLoader(void *meshlite, int meshId, int triangulatedMeshId=-1, QColor defaultColor=Theme::white, const std::vector *triangleMaterials=nullptr, bool smoothNormal=true); MeshLoader(MeshResultContext &resultContext); MeshLoader(Vertex *triangleVertices, int vertexNum); MeshLoader(const MeshLoader &mesh); @@ -56,6 +59,19 @@ public: const std::vector &triangulatedFaces(); void setTextureImage(QImage *textureImage); const QImage *textureImage(); + void setNormalMapImage(QImage *normalMapImage); + const QImage *normalMapImage(); + const QImage *metalnessRoughnessAmbientOcclusionImage(); + void setMetalnessRoughnessAmbientOcclusionImage(QImage *image); + bool hasMetalnessInImage(); + void setHasMetalnessInImage(bool hasInImage); + bool hasRoughnessInImage(); + void setHasRoughnessInImage(bool hasInImage); + bool hasAmbientOcclusionInImage(); + void setHasAmbientOcclusionInImage(bool hasInImage); + static float m_defaultMetalness; + static float m_defaultRoughness; + void exportAsObj(const QString &filename); private: Vertex *m_triangleVertices = nullptr; int m_triangleVertexCount = 0; @@ -66,6 +82,11 @@ private: std::vector m_triangulatedVertices; std::vector m_triangulatedFaces; QImage *m_textureImage = nullptr; + QImage *m_normalMapImage = nullptr; + QImage *m_metalnessRoughnessAmbientOcclusionImage = nullptr; + bool m_hasMetalnessInImage = false; + bool m_hasRoughnessInImage = false; + bool m_hasAmbientOcclusionInImage = false; }; #endif diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp index 850b6430..95b211e8 100644 --- a/src/meshresultcontext.cpp +++ b/src/meshresultcontext.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include "texturegenerator.h" #include "theme.h" #include "meshresultcontext.h" #include "thekla_atlas.h" @@ -32,7 +34,8 @@ MeshResultContext::MeshResultContext() : m_resultPartsResolved(false), m_resultTriangleUvsResolved(false), m_resultRearrangedVerticesResolved(false), - m_vertexNormalsInterpolated(false) + m_vertexNormalsInterpolated(false), + m_triangleTangentsResolved(false) { } @@ -351,6 +354,7 @@ void MeshResultContext::calculateResultParts(std::map &parts) } resultPart.triangles.push_back(newTriangle); resultPart.uvs.push_back(triangleUvs()[x]); + resultPart.triangleTangents.push_back(triangleTangents()[x]); } } @@ -432,7 +436,9 @@ void MeshResultContext::calculateResultTriangleUvs(std::vector Atlas_Options atlasOptions; atlas_set_default_options(&atlasOptions); - atlasOptions.packer_options.witness.packing_quality = 4; + atlasOptions.packer_options.witness.packing_quality = 1; + //atlasOptions.packer_options.witness.texel_area = 1.0 / TextureGenerator::m_textureSize; + atlasOptions.packer_options.witness.conservative = false; Atlas_Error error = Atlas_Error_Success; Atlas_Output_Mesh *outputMesh = atlas_generate(&inputMesh, &atlasOptions, &error); @@ -555,3 +561,45 @@ const std::vector &MeshResultContext::interpolatedVertexNormals() } return m_interpolatedVertexNormals; } + +const std::vector &MeshResultContext::triangleTangents() +{ + if (!m_triangleTangentsResolved) { + m_triangleTangentsResolved = true; + calculateTriangleTangents(m_triangleTangents); + } + return m_triangleTangents; +} + +void MeshResultContext::calculateTriangleTangents(std::vector &tangents) +{ + tangents.resize(triangles.size()); + + for (decltype(triangles.size()) i = 0; i < triangles.size(); i++) { + tangents[i] = {0, 0, 0}; + const auto &uv = triangleUvs()[i]; + if (!uv.resolved) + continue; + QVector2D uv1 = {uv.uv[0][0], uv.uv[0][1]}; + QVector2D uv2 = {uv.uv[1][0], uv.uv[1][1]}; + QVector2D uv3 = {uv.uv[2][0], uv.uv[2][1]}; + const auto &triangle = triangles[i]; + const QVector3D &pos1 = vertices[triangle.indicies[0]].position; + const QVector3D &pos2 = vertices[triangle.indicies[1]].position; + const QVector3D &pos3 = vertices[triangle.indicies[2]].position; + QVector3D edge1 = pos2 - pos1; + QVector3D edge2 = pos3 - pos1; + QVector2D deltaUv1 = uv2 - uv1; + QVector2D deltaUv2 = uv3 - uv1; + auto bottom = deltaUv1.x() * deltaUv2.y() - deltaUv2.x() * deltaUv1.y(); + if (qFuzzyIsNull(bottom)) + continue; + float f = 1.0 / bottom; + QVector3D tangent = { + f * (deltaUv2.y() * edge1.x() - deltaUv1.y() * edge2.x()), + f * (deltaUv2.y() * edge1.y() - deltaUv1.y() * edge2.y()), + f * (deltaUv2.y() * edge1.z() - deltaUv1.y() * edge2.z()) + }; + tangents[i] = tangent.normalized(); + } +} diff --git a/src/meshresultcontext.h b/src/meshresultcontext.h index acf1358b..ad84e3f8 100644 --- a/src/meshresultcontext.h +++ b/src/meshresultcontext.h @@ -7,14 +7,14 @@ #include #include "positionmap.h" #include "skeletonbonemark.h" +#include "texturetype.h" #define MAX_WEIGHT_NUM 4 struct Material { QColor color; - float metalness; - float roughness; + const QImage *textureImages[(int)TextureType::Count - 1] = {nullptr}; }; struct BmeshNode @@ -65,6 +65,7 @@ struct ResultPart std::vector triangles; std::vector uvs; std::vector vertexUvs; + std::vector triangleTangents; }; struct ResultRearrangedVertex @@ -99,6 +100,7 @@ public: const std::vector &rearrangedTriangles(); const std::map> &vertexSourceMap(); const std::vector &interpolatedVertexNormals(); + const std::vector &triangleTangents(); private: bool m_triangleSourceResolved; bool m_triangleMaterialResolved; @@ -108,6 +110,7 @@ private: bool m_resultTriangleUvsResolved; bool m_resultRearrangedVerticesResolved; bool m_vertexNormalsInterpolated; + bool m_triangleTangentsResolved; private: std::vector> m_triangleSourceNodes; std::vector m_triangleMaterials; @@ -121,6 +124,7 @@ private: std::map> m_vertexSourceMap; std::map m_rearrangedVerticesToOldIndexMap; std::vector m_interpolatedVertexNormals; + std::vector m_triangleTangents; private: void calculateTriangleSourceNodes(std::vector> &triangleSourceNodes, std::map> &vertexSourceMap); void calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map> &vertexSourceMap); @@ -131,6 +135,7 @@ private: void calculateResultTriangleUvs(std::vector &uvs, std::set &seamVertices); void calculateResultRearrangedVertices(std::vector &rearrangedVertices, std::vector &rearrangedTriangles); void interpolateVertexNormals(std::vector &resultNormals); + void calculateTriangleTangents(std::vector &tangents); }; #endif diff --git a/src/meshresultpostprocessor.cpp b/src/meshresultpostprocessor.cpp index f6fddd08..3a241fbc 100644 --- a/src/meshresultpostprocessor.cpp +++ b/src/meshresultpostprocessor.cpp @@ -24,6 +24,7 @@ void MeshResultPostProcessor::process() if (!m_meshResultContext->bmeshNodes.empty()) { m_meshResultContext->rearrangedVertices(); m_meshResultContext->rearrangedTriangles(); + (void)m_meshResultContext->triangleTangents(); m_meshResultContext->parts(); } diff --git a/src/modelmeshbinder.cpp b/src/modelmeshbinder.cpp index c8bb8383..15578a7d 100644 --- a/src/modelmeshbinder.cpp +++ b/src/modelmeshbinder.cpp @@ -16,7 +16,13 @@ ModelMeshBinder::ModelMeshBinder() : m_newMeshComing(false), m_showWireframes(false), m_hasTexture(false), - m_texture(nullptr) + m_texture(nullptr), + m_hasNormalMap(false), + m_normalMap(nullptr), + m_hasMetalnessMap(false), + m_hasRoughnessMap(false), + m_hasAmbientOcclusionMap(false), + m_metalnessRoughnessAmbientOcclusionMap(nullptr) { } @@ -25,6 +31,8 @@ ModelMeshBinder::~ModelMeshBinder() delete m_mesh; delete m_newMesh; delete m_texture; + delete m_normalMap; + delete m_metalnessRoughnessAmbientOcclusionMap; } void ModelMeshBinder::updateMesh(MeshLoader *mesh) @@ -37,77 +45,6 @@ void ModelMeshBinder::updateMesh(MeshLoader *mesh) } } -void ModelMeshBinder::exportMeshAsObj(const QString &filename) -{ - QMutexLocker lock(&m_meshMutex); - if (m_mesh) { - QFile file(filename); - if (file.open(QIODevice::WriteOnly)) { - QTextStream stream(&file); - stream << "# " << Ds3FileReader::m_applicationName << endl; - for (std::vector::const_iterator it = m_mesh->vertices().begin() ; it != m_mesh->vertices().end(); ++it) { - stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl; - } - for (std::vector>::const_iterator it = m_mesh->faces().begin() ; it != m_mesh->faces().end(); ++it) { - stream << "f"; - for (std::vector::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) { - stream << " " << (1 + *subIt); - } - stream << endl; - } - } - } -} - -void ModelMeshBinder::exportMeshAsObjPlusMaterials(const QString &filename) -{ - QMutexLocker lock(&m_meshMutex); - if (m_mesh) { - QFileInfo nameInfo(filename); - QString mtlFilenameWithoutPath = nameInfo.baseName() + ".mtl"; - QString mtlFilename = nameInfo.path() + QDir::separator() + mtlFilenameWithoutPath; - std::map colorNameMap; - QString lastColorName; - - qDebug() << "export obj to " << filename; - qDebug() << "export mtl to " << mtlFilename; - - QFile file(filename); - if (file.open(QIODevice::WriteOnly)) { - QTextStream stream(&file); - stream << "# " << Ds3FileReader::m_applicationName << endl; - stream << "mtllib " << mtlFilenameWithoutPath << endl; - for (std::vector::const_iterator it = m_mesh->triangulatedVertices().begin() ; it != m_mesh->triangulatedVertices().end(); ++it) { - stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl; - } - for (std::vector::const_iterator it = m_mesh->triangulatedFaces().begin() ; it != m_mesh->triangulatedFaces().end(); ++it) { - QString colorName = it->color.name(); - colorName = "rgb" + colorName.remove(QChar('#')); - if (colorNameMap.find(colorName) == colorNameMap.end()) - colorNameMap[colorName] = it->color; - if (lastColorName != colorName) { - lastColorName = colorName; - stream << "usemtl " << colorName << endl; - } - stream << "f" << " " << (1 + it->indicies[0]) << " " << (1 + it->indicies[1]) << " " << (1 + it->indicies[2]) << endl; - } - } - - QFile mtlFile(mtlFilename); - if (mtlFile.open(QIODevice::WriteOnly)) { - QTextStream stream(&mtlFile); - stream << "# " << Ds3FileReader::m_applicationName << endl; - for (const auto &it: colorNameMap) { - stream << "newmtl " << it.first << endl; - stream << "Ka" << " " << it.second.redF() << " " << it.second.greenF() << " " << it.second.blueF() << endl; - stream << "Kd" << " " << it.second.redF() << " " << it.second.greenF() << " " << it.second.blueF() << endl; - stream << "Ks" << " 0.0 0.0 0.0" << endl; - stream << "illum" << " 1" << endl; - } - } - } -} - void ModelMeshBinder::initialize() { m_vaoTriangle.create(); @@ -133,12 +70,28 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) delete m_mesh; m_mesh = newMesh; if (m_mesh) { + m_hasTexture = nullptr != m_mesh->textureImage(); delete m_texture; m_texture = nullptr; - if (m_hasTexture) { + if (m_hasTexture) m_texture = new QOpenGLTexture(*m_mesh->textureImage()); - } + + m_hasNormalMap = nullptr != m_mesh->normalMapImage(); + delete m_normalMap; + m_normalMap = nullptr; + if (m_hasNormalMap) + m_normalMap = new QOpenGLTexture(*m_mesh->normalMapImage()); + + m_hasMetalnessMap = m_mesh->hasMetalnessInImage(); + m_hasRoughnessMap = m_mesh->hasRoughnessInImage(); + m_hasAmbientOcclusionMap = m_mesh->hasAmbientOcclusionInImage(); + delete m_metalnessRoughnessAmbientOcclusionMap; + m_metalnessRoughnessAmbientOcclusionMap = nullptr; + if (nullptr != m_mesh->metalnessRoughnessAmbientOcclusionImage() && + (m_hasMetalnessMap || m_hasRoughnessMap || m_hasAmbientOcclusionMap)) + m_metalnessRoughnessAmbientOcclusionMap = new QOpenGLTexture(*m_mesh->metalnessRoughnessAmbientOcclusionImage()); + { QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoTriangle); if (m_vboTriangle.isCreated()) @@ -154,12 +107,14 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) f->glEnableVertexAttribArray(3); f->glEnableVertexAttribArray(4); f->glEnableVertexAttribArray(5); + f->glEnableVertexAttribArray(6); f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(3 * sizeof(GLfloat))); f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(6 * sizeof(GLfloat))); f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(9 * sizeof(GLfloat))); f->glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(11 * sizeof(GLfloat))); f->glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(12 * sizeof(GLfloat))); + f->glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(13 * sizeof(GLfloat))); m_vboTriangle.release(); } { @@ -177,12 +132,14 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) f->glEnableVertexAttribArray(3); f->glEnableVertexAttribArray(4); f->glEnableVertexAttribArray(5); + f->glEnableVertexAttribArray(6); f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(3 * sizeof(GLfloat))); f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(6 * sizeof(GLfloat))); f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(9 * sizeof(GLfloat))); f->glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(11 * sizeof(GLfloat))); f->glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(12 * sizeof(GLfloat))); + f->glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(13 * sizeof(GLfloat))); m_vboEdge.release(); } } else { @@ -197,6 +154,10 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoEdge); QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); program->setUniformValue(program->textureEnabledLoc(), 0); + program->setUniformValue(program->normalMapEnabledLoc(), 0); + program->setUniformValue(program->metalnessMapEnabledLoc(), 0); + program->setUniformValue(program->roughnessMapEnabledLoc(), 0); + program->setUniformValue(program->ambientOcclusionMapEnabledLoc(), 0); f->glDrawArrays(GL_LINES, 0, m_renderEdgeVertexCount); } } @@ -211,6 +172,22 @@ void ModelMeshBinder::paint(ModelShaderProgram *program) } else { program->setUniformValue(program->textureEnabledLoc(), 0); } + if (m_hasNormalMap) { + if (m_normalMap) + m_normalMap->bind(1); + program->setUniformValue(program->normalMapIdLoc(), 1); + program->setUniformValue(program->normalMapEnabledLoc(), 1); + } else { + program->setUniformValue(program->normalMapEnabledLoc(), 0); + } + if (m_hasMetalnessMap || m_hasRoughnessMap || m_hasAmbientOcclusionMap) { + if (m_metalnessRoughnessAmbientOcclusionMap) + m_metalnessRoughnessAmbientOcclusionMap->bind(2); + program->setUniformValue(program->metalnessRoughnessAmbientOcclusionMapIdLoc(), 2); + } + program->setUniformValue(program->metalnessMapEnabledLoc(), m_hasMetalnessMap ? 1 : 0); + program->setUniformValue(program->roughnessMapEnabledLoc(), m_hasRoughnessMap ? 1 : 0); + program->setUniformValue(program->ambientOcclusionMapEnabledLoc(), m_hasAmbientOcclusionMap ? 1 : 0); f->glDrawArrays(GL_TRIANGLES, 0, m_renderTriangleVertexCount); } } @@ -223,6 +200,10 @@ void ModelMeshBinder::cleanup() m_vboEdge.destroy(); delete m_texture; m_texture = nullptr; + delete m_normalMap; + m_normalMap = nullptr; + delete m_metalnessRoughnessAmbientOcclusionMap; + m_metalnessRoughnessAmbientOcclusionMap = nullptr; } void ModelMeshBinder::showWireframes() diff --git a/src/modelmeshbinder.h b/src/modelmeshbinder.h index 721ed085..975c9da5 100644 --- a/src/modelmeshbinder.h +++ b/src/modelmeshbinder.h @@ -14,8 +14,6 @@ public: ModelMeshBinder(); ~ModelMeshBinder(); void updateMesh(MeshLoader *mesh); - void exportMeshAsObj(const QString &filename); - void exportMeshAsObjPlusMaterials(const QString &filename); void initialize(); void paint(ModelShaderProgram *program); void cleanup(); @@ -31,6 +29,12 @@ private: bool m_showWireframes; bool m_hasTexture; QOpenGLTexture *m_texture; + bool m_hasNormalMap; + QOpenGLTexture *m_normalMap; + bool m_hasMetalnessMap; + bool m_hasRoughnessMap; + bool m_hasAmbientOcclusionMap; + QOpenGLTexture *m_metalnessRoughnessAmbientOcclusionMap; private: QOpenGLVertexArrayObject m_vaoTriangle; QOpenGLBuffer m_vboTriangle; diff --git a/src/modelshaderprogram.cpp b/src/modelshaderprogram.cpp index cf29a264..94d55fcb 100644 --- a/src/modelshaderprogram.cpp +++ b/src/modelshaderprogram.cpp @@ -36,15 +36,23 @@ ModelShaderProgram::ModelShaderProgram(bool usePBR) this->bindAttributeLocation("texCoord", 3); this->bindAttributeLocation("metalness", 4); this->bindAttributeLocation("roughness", 5); + this->bindAttributeLocation("tangent", 6); this->link(); this->bind(); m_projectionMatrixLoc = this->uniformLocation("projectionMatrix"); m_modelMatrixLoc = this->uniformLocation("modelMatrix"); + m_normalMatrixLoc = this->uniformLocation("normalMatrix"); m_viewMatrixLoc = this->uniformLocation("viewMatrix"); m_lightPosLoc = this->uniformLocation("lightPos"); m_textureIdLoc = this->uniformLocation("textureId"); m_textureEnabledLoc = this->uniformLocation("textureEnabled"); + m_normalMapIdLoc = this->uniformLocation("normalMapId"); + m_normalMapEnabledLoc = this->uniformLocation("normalMapEnabled"); + m_metalnessMapEnabledLoc = this->uniformLocation("metalnessMapEnabled"); + m_roughnessMapEnabledLoc = this->uniformLocation("roughnessMapEnabled"); + m_ambientOcclusionMapEnabledLoc = this->uniformLocation("ambientOcclusionMapEnabled"); + m_metalnessRoughnessAmbientOcclusionMapIdLoc = this->uniformLocation("metalnessRoughnessAmbientOcclusionMapId"); } int ModelShaderProgram::projectionMatrixLoc() @@ -57,6 +65,11 @@ int ModelShaderProgram::modelMatrixLoc() return m_modelMatrixLoc; } +int ModelShaderProgram::normalMatrixLoc() +{ + return m_normalMatrixLoc; +} + int ModelShaderProgram::viewMatrixLoc() { return m_viewMatrixLoc; @@ -76,3 +89,34 @@ int ModelShaderProgram::textureIdLoc() { return m_textureIdLoc; } + +int ModelShaderProgram::normalMapEnabledLoc() +{ + return m_normalMapEnabledLoc; +} + +int ModelShaderProgram::normalMapIdLoc() +{ + return m_normalMapIdLoc; +} + +int ModelShaderProgram::metalnessMapEnabledLoc() +{ + return m_metalnessMapEnabledLoc; +} + +int ModelShaderProgram::roughnessMapEnabledLoc() +{ + return m_roughnessMapEnabledLoc; +} + +int ModelShaderProgram::ambientOcclusionMapEnabledLoc() +{ + return m_ambientOcclusionMapEnabledLoc; +} + +int ModelShaderProgram::metalnessRoughnessAmbientOcclusionMapIdLoc() +{ + return m_metalnessRoughnessAmbientOcclusionMapIdLoc; +} + diff --git a/src/modelshaderprogram.h b/src/modelshaderprogram.h index 1cfb20ca..62c31d93 100644 --- a/src/modelshaderprogram.h +++ b/src/modelshaderprogram.h @@ -9,18 +9,32 @@ public: ModelShaderProgram(bool usePBR=true); int projectionMatrixLoc(); int modelMatrixLoc(); + int normalMatrixLoc(); int viewMatrixLoc(); int lightPosLoc(); int textureIdLoc(); int textureEnabledLoc(); + int normalMapEnabledLoc(); + int normalMapIdLoc(); + int metalnessMapEnabledLoc(); + int roughnessMapEnabledLoc(); + int ambientOcclusionMapEnabledLoc(); + int metalnessRoughnessAmbientOcclusionMapIdLoc(); static const QString &loadShaderSource(const QString &name); private: int m_projectionMatrixLoc; int m_modelMatrixLoc; + int m_normalMatrixLoc; int m_viewMatrixLoc; int m_lightPosLoc; int m_textureIdLoc; int m_textureEnabledLoc; + int m_normalMapEnabledLoc; + int m_normalMapIdLoc; + int m_metalnessMapEnabledLoc; + int m_roughnessMapEnabledLoc; + int m_ambientOcclusionMapEnabledLoc; + int m_metalnessRoughnessAmbientOcclusionMapIdLoc; }; #endif diff --git a/src/modelwidget.cpp b/src/modelwidget.cpp index 1d69c658..0110a559 100644 --- a/src/modelwidget.cpp +++ b/src/modelwidget.cpp @@ -136,9 +136,11 @@ void ModelWidget::initializeGL() // Our camera never changes in this example. m_camera.setToIdentity(); + // FIXME: if change here, please also change the camera pos in PBR shader m_camera.translate(0, 0, -2.1); // Light position is fixed. + // FIXME: PBR render no longer use this parameter m_program->setUniformValue(m_program->lightPosLoc(), QVector3D(0, 0, 70)); m_program->release(); @@ -159,8 +161,11 @@ void ModelWidget::paintGL() m_program->bind(); m_program->setUniformValue(m_program->projectionMatrixLoc(), m_projection); m_program->setUniformValue(m_program->modelMatrixLoc(), m_world); + QMatrix3x3 normalMatrix = m_world.normalMatrix(); + m_program->setUniformValue(m_program->normalMatrixLoc(), normalMatrix); m_program->setUniformValue(m_program->viewMatrixLoc(), m_camera); m_program->setUniformValue(m_program->textureEnabledLoc(), 0); + m_program->setUniformValue(m_program->normalMapEnabledLoc(), 0); m_meshBinder.paint(m_program); @@ -291,16 +296,6 @@ void ModelWidget::updateMesh(MeshLoader *mesh) update(); } -void ModelWidget::exportMeshAsObj(const QString &filename) -{ - m_meshBinder.exportMeshAsObj(filename); -} - -void ModelWidget::exportMeshAsObjPlusMaterials(const QString &filename) -{ - m_meshBinder.exportMeshAsObjPlusMaterials(filename); -} - void ModelWidget::enableMove(bool enabled) { m_moveEnabled = enabled; diff --git a/src/modelwidget.h b/src/modelwidget.h index 2b43ff07..9b92cd22 100644 --- a/src/modelwidget.h +++ b/src/modelwidget.h @@ -28,8 +28,6 @@ public: m_transparent = t; } void updateMesh(MeshLoader *mesh); - void exportMeshAsObj(const QString &filename); - void exportMeshAsObjPlusMaterials(const QString &filename); void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions); void toggleWireframe(); void enableMove(bool enabled); diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index c1df6b3b..ff114b3e 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -9,6 +9,7 @@ #include "skeletondocument.h" #include "dust3dutil.h" #include "skeletonxml.h" +#include "materialpreviewsgenerator.h" unsigned long SkeletonDocument::m_maxSnapshot = 1000; @@ -54,7 +55,8 @@ SkeletonDocument::SkeletonDocument() : m_isRigObsolete(false), m_riggedResultContext(new MeshResultContext), m_posePreviewsGenerator(nullptr), - m_currentRigSucceed(false) + m_currentRigSucceed(false), + m_materialPreviewsGenerator(nullptr) { } @@ -592,6 +594,14 @@ const SkeletonPose *SkeletonDocument::findPose(QUuid poseId) const return &it->second; } +const SkeletonMaterial *SkeletonDocument::findMaterial(QUuid materialId) const +{ + auto it = materialMap.find(materialId); + if (it == materialMap.end()) + return nullptr; + return &it->second; +} + const SkeletonMotion *SkeletonDocument::findMotion(QUuid motionId) const { auto it = motionMap.find(motionId); @@ -830,7 +840,10 @@ void SkeletonDocument::markAllDirty() } void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set &limitNodeIds, - SkeletonDocumentToSnapshotFor forWhat, const std::set &limitPoseIds, const std::set &limitMotionIds) const + SkeletonDocumentToSnapshotFor forWhat, + const std::set &limitPoseIds, + const std::set &limitMotionIds, + const std::set &limitMaterialIds) const { if (SkeletonDocumentToSnapshotFor::Document == forWhat || SkeletonDocumentToSnapshotFor::Nodes == forWhat) { @@ -874,10 +887,8 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setparts[part["id"]] = part; } for (const auto &nodeIt: nodeMap) { @@ -952,6 +963,38 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setrootComponent["children"] = children; } } + if (SkeletonDocumentToSnapshotFor::Document == forWhat || + SkeletonDocumentToSnapshotFor::Materials == forWhat) { + for (const auto &materialId: materialIdList) { + if (!limitMaterialIds.empty() && limitMaterialIds.find(materialId) == limitMaterialIds.end()) + continue; + auto findMaterialResult = materialMap.find(materialId); + if (findMaterialResult == materialMap.end()) { + qDebug() << "Find material failed:" << materialId; + continue; + } + auto &materialIt = *findMaterialResult; + std::map material; + material["id"] = materialIt.second.id.toString(); + material["type"] = "MetalRoughness"; + if (!materialIt.second.name.isEmpty()) + material["name"] = materialIt.second.name; + std::vector, std::vector>>> layers; + for (const auto &layer: materialIt.second.layers) { + std::vector> maps; + for (const auto &mapItem: layer.maps) { + std::map textureMap; + textureMap["for"] = TextureTypeToString(mapItem.forWhat); + textureMap["linkDataType"] = "imageId"; + textureMap["linkData"] = mapItem.imageId.toString(); + maps.push_back(textureMap); + } + std::map layerAttributes; + layers.push_back({layerAttributes, maps}); + } + snapshot->materials.push_back(std::make_pair(material, layers)); + } + } if (SkeletonDocumentToSnapshotFor::Document == forWhat || SkeletonDocumentToSnapshotFor::Poses == forWhat) { for (const auto &poseId: poseIdList) { @@ -1048,6 +1091,43 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr std::set inversePartIds; std::map oldNewIdMap; + for (const auto &materialIt: snapshot.materials) { + const auto &materialAttributes = materialIt.first; + auto materialType = valueOfKeyInMapOrEmpty(materialAttributes, "type"); + if ("MetalRoughness" != materialType) { + qDebug() << "Unsupported material type:" << materialType; + continue; + } + QUuid newMaterialId = QUuid::createUuid(); + auto &newMaterial = materialMap[newMaterialId]; + newMaterial.id = newMaterialId; + newMaterial.name = valueOfKeyInMapOrEmpty(materialAttributes, "name"); + oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(materialAttributes, "id"))] = newMaterialId; + for (const auto &layerIt: materialIt.second) { + SkeletonMaterialLayer layer; + for (const auto &mapItem: layerIt.second) { + auto textureTypeString = valueOfKeyInMapOrEmpty(mapItem, "for"); + auto textureType = TextureTypeFromString(textureTypeString.toUtf8().constData()); + if (TextureType::None == textureType) { + qDebug() << "Unsupported texture type:" << textureTypeString; + continue; + } + auto linkTypeString = valueOfKeyInMapOrEmpty(mapItem, "linkDataType"); + if ("imageId" != linkTypeString) { + qDebug() << "Unsupported link data type:" << linkTypeString; + continue; + } + auto imageId = QUuid(valueOfKeyInMapOrEmpty(mapItem, "linkData")); + SkeletonMaterialMap materialMap; + materialMap.imageId = imageId; + materialMap.forWhat = textureType; + layer.maps.push_back(materialMap); + } + newMaterial.layers.push_back(layer); + } + materialIdList.push_back(newMaterialId); + emit materialAdded(newMaterialId); + } for (const auto &partKv: snapshot.parts) { const auto newUuid = QUuid::createUuid(); SkeletonPart &part = partMap[newUuid]; @@ -1075,12 +1155,9 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr const auto &deformWidthIt = partKv.second.find("deformWidth"); if (deformWidthIt != partKv.second.end()) part.setDeformWidth(deformWidthIt->second.toFloat()); - const auto &metalnessIt = partKv.second.find("metalness"); - if (metalnessIt != partKv.second.end()) - part.metalness = metalnessIt->second.toFloat(); - const auto &roughnessIt = partKv.second.find("roughness"); - if (roughnessIt != partKv.second.end()) - part.roughness = roughnessIt->second.toFloat(); + const auto &materialIdIt = partKv.second.find("materialId"); + if (materialIdIt != partKv.second.end()) + part.materialId = oldNewIdMap[QUuid(materialIdIt->second)]; newAddedPartIds.insert(part.id); } for (const auto &nodeKv: snapshot.nodes) { @@ -1263,6 +1340,8 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr emit checkEdge(edgeIt); } + if (!snapshot.materials.empty()) + emit materialListChanged(); if (!snapshot.poses.empty()) emit poseListChanged(); if (!snapshot.motions.empty()) @@ -1279,6 +1358,8 @@ void SkeletonDocument::reset() edgeMap.clear(); partMap.clear(); componentMap.clear(); + materialMap.clear(); + materialIdList.clear(); poseMap.clear(); poseIdList.clear(); motionMap.clear(); @@ -1305,8 +1386,9 @@ MeshLoader *SkeletonDocument::takeResultMesh() MeshLoader *SkeletonDocument::takeResultTextureMesh() { - MeshLoader *resultTextureMesh = m_resultTextureMesh; - m_resultTextureMesh = nullptr; + if (nullptr == m_resultTextureMesh) + return nullptr; + MeshLoader *resultTextureMesh = new MeshLoader(*m_resultTextureMesh); return resultTextureMesh; } @@ -1413,20 +1495,21 @@ void SkeletonDocument::generateMesh() SkeletonSnapshot *snapshot = new SkeletonSnapshot; toSnapshot(snapshot); resetDirtyFlags(); - m_meshGenerator = new MeshGenerator(snapshot, thread); + m_meshGenerator = new MeshGenerator(snapshot); m_meshGenerator->setSmoothNormal(m_smoothNormal); m_meshGenerator->setWeldEnabled(weldEnabled); m_meshGenerator->setGeneratedCacheContext(&m_generatedCacheContext); if (nullptr != m_sharedContextWidget) m_meshGenerator->setSharedContextWidget(m_sharedContextWidget); - m_meshGenerator->moveToThread(thread); for (auto &part: partMap) { m_meshGenerator->addPartPreviewRequirement(part.first); } + m_meshGenerator->moveToThread(thread); connect(thread, &QThread::started, m_meshGenerator, &MeshGenerator::process); connect(m_meshGenerator, &MeshGenerator::finished, this, &SkeletonDocument::meshReady); connect(m_meshGenerator, &MeshGenerator::finished, thread, &QThread::quit); connect(thread, &QThread::finished, thread, &QThread::deleteLater); + emit meshGenerating(); thread->start(); } @@ -1442,12 +1525,31 @@ void SkeletonDocument::generateTexture() m_isTextureObsolete = false; QThread *thread = new QThread; - m_textureGenerator = new TextureGenerator(*m_postProcessedResultContext, thread); + m_textureGenerator = new TextureGenerator(*m_postProcessedResultContext); + for (const auto &bmeshNode: m_postProcessedResultContext->bmeshNodes) { + for (size_t i = 0; i < sizeof(bmeshNode.material.textureImages) / sizeof(bmeshNode.material.textureImages[0]); ++i) { + TextureType forWhat = (TextureType)(i + 1); + const QImage *image = bmeshNode.material.textureImages[i]; + if (nullptr != image) { + if (TextureType::BaseColor == forWhat) + m_textureGenerator->addPartColorMap(bmeshNode.partId, image); + else if (TextureType::Normal == forWhat) + m_textureGenerator->addPartNormalMap(bmeshNode.partId, image); + else if (TextureType::Metalness == forWhat) + m_textureGenerator->addPartMetalnessMap(bmeshNode.partId, image); + else if (TextureType::Roughness == forWhat) + m_textureGenerator->addPartRoughnessMap(bmeshNode.partId, image); + else if (TextureType::AmbientOcclusion == forWhat) + m_textureGenerator->addPartAmbientOcclusionMap(bmeshNode.partId, image); + } + } + } m_textureGenerator->moveToThread(thread); connect(thread, &QThread::started, m_textureGenerator, &TextureGenerator::process); connect(m_textureGenerator, &TextureGenerator::finished, this, &SkeletonDocument::textureReady); connect(m_textureGenerator, &TextureGenerator::finished, thread, &QThread::quit); connect(thread, &QThread::finished, thread, &QThread::deleteLater); + emit textureGenerating(); thread->start(); } @@ -1501,7 +1603,7 @@ void SkeletonDocument::bakeAmbientOcclusionTexture() QThread *thread = new QThread; m_ambientOcclusionBaker = new AmbientOcclusionBaker(); m_ambientOcclusionBaker->setInputMesh(*m_postProcessedResultContext); - m_ambientOcclusionBaker->setBakeSize(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight); + m_ambientOcclusionBaker->setBakeSize(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize); if (textureBorderImage) m_ambientOcclusionBaker->setBorderImage(*textureBorderImage); if (textureColorImage) @@ -1578,6 +1680,7 @@ void SkeletonDocument::postProcess() connect(m_postProcessor, &MeshResultPostProcessor::finished, this, &SkeletonDocument::postProcessedMeshResultReady); connect(m_postProcessor, &MeshResultPostProcessor::finished, thread, &QThread::quit); connect(thread, &QThread::finished, thread, &QThread::deleteLater); + emit postProcessing(); thread->start(); } @@ -2155,29 +2258,18 @@ void SkeletonDocument::setPartDeformWidth(QUuid partId, float width) emit skeletonChanged(); } -void SkeletonDocument::setPartMetalness(QUuid partId, float metalness) +void SkeletonDocument::setPartMaterialId(QUuid partId, QUuid materialId) { auto part = partMap.find(partId); if (part == partMap.end()) { qDebug() << "Part not found:" << partId; return; } - part->second.metalness = metalness; - part->second.dirty = true; - emit partMetalnessChanged(partId); - emit skeletonChanged(); -} - -void SkeletonDocument::setPartRoughness(QUuid partId, float roughness) -{ - auto part = partMap.find(partId); - if (part == partMap.end()) { - qDebug() << "Part not found:" << partId; + if (part->second.materialId == materialId) return; - } - part->second.roughness = roughness; + part->second.materialId = materialId; part->second.dirty = true; - emit partRoughnessChanged(partId); + emit partMaterialIdChanged(partId); emit skeletonChanged(); } @@ -2280,6 +2372,17 @@ bool SkeletonDocument::hasPastableNodesInClipboard() const return false; } +bool SkeletonDocument::hasPastableMaterialsInClipboard() const +{ + const QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(); + if (mimeData->hasText()) { + if (-1 != mimeData->text().indexOf(" layers) +{ + QUuid newMaterialId = QUuid::createUuid(); + auto &material = materialMap[newMaterialId]; + material.id = newMaterialId; + + material.name = name; + material.layers = layers; + material.dirty = true; + + materialIdList.push_back(newMaterialId); + + emit materialAdded(newMaterialId); + emit materialListChanged(); + emit optionsChanged(); +} + +void SkeletonDocument::removeMaterial(QUuid materialId) +{ + auto findMaterialResult = materialMap.find(materialId); + if (findMaterialResult == materialMap.end()) { + qDebug() << "Remove a none exist material:" << materialId; + return; + } + materialIdList.erase(std::remove(materialIdList.begin(), materialIdList.end(), materialId), materialIdList.end()); + materialMap.erase(findMaterialResult); + + emit materialListChanged(); + emit materialRemoved(materialId); + emit optionsChanged(); +} + +void SkeletonDocument::setMaterialLayers(QUuid materialId, std::vector layers) +{ + auto findMaterialResult = materialMap.find(materialId); + if (findMaterialResult == materialMap.end()) { + qDebug() << "Find material failed:" << materialId; + return; + } + findMaterialResult->second.layers = layers; + findMaterialResult->second.dirty = true; + emit materialLayersChanged(materialId); + emit optionsChanged(); +} + +void SkeletonDocument::renameMaterial(QUuid materialId, QString name) +{ + auto findMaterialResult = materialMap.find(materialId); + if (findMaterialResult == materialMap.end()) { + qDebug() << "Find material failed:" << materialId; + return; + } + if (findMaterialResult->second.name == name) + return; + + findMaterialResult->second.name = name; + emit materialNameChanged(materialId); + emit optionsChanged(); +} + +void SkeletonDocument::generateMaterialPreviews() +{ + if (nullptr != m_materialPreviewsGenerator) { + return; + } + + QThread *thread = new QThread; + m_materialPreviewsGenerator = new MaterialPreviewsGenerator(); + bool hasDirtyMaterial = false; + for (auto &materialIt: materialMap) { + if (!materialIt.second.dirty) + continue; + m_materialPreviewsGenerator->addMaterial(materialIt.first, materialIt.second.layers); + materialIt.second.dirty = false; + hasDirtyMaterial = true; + } + if (!hasDirtyMaterial) { + delete m_materialPreviewsGenerator; + m_materialPreviewsGenerator = nullptr; + delete thread; + return; + } + + qDebug() << "Material previews generating.."; + + m_materialPreviewsGenerator->moveToThread(thread); + connect(thread, &QThread::started, m_materialPreviewsGenerator, &MaterialPreviewsGenerator::process); + connect(m_materialPreviewsGenerator, &MaterialPreviewsGenerator::finished, this, &SkeletonDocument::materialPreviewsReady); + connect(m_materialPreviewsGenerator, &MaterialPreviewsGenerator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +void SkeletonDocument::materialPreviewsReady() +{ + for (const auto &materialId: m_materialPreviewsGenerator->generatedPreviewMaterialIds()) { + auto material = materialMap.find(materialId); + if (material != materialMap.end()) { + MeshLoader *resultPartPreviewMesh = m_materialPreviewsGenerator->takePreview(materialId); + material->second.updatePreviewMesh(resultPartPreviewMesh); + emit materialPreviewChanged(materialId); + } + } + + delete m_materialPreviewsGenerator; + m_materialPreviewsGenerator = nullptr; + + qDebug() << "Material previews generation done"; + + generateMaterialPreviews(); +} + diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 26532ad9..90110bbe 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -22,6 +22,9 @@ #include "rigtype.h" #include "posepreviewsgenerator.h" #include "curveutil.h" +#include "texturetype.h" + +class MaterialPreviewsGenerator; class SkeletonNode { @@ -97,8 +100,7 @@ public: std::vector nodeIds; bool dirty; bool wrapped; - float metalness; - float roughness; + QUuid materialId; SkeletonPart(const QUuid &withId=QUuid()) : visible(true), locked(false), @@ -112,9 +114,7 @@ public: color(Theme::white), hasColor(false), dirty(true), - wrapped(false), - metalness(0.0), - roughness(1.0) + wrapped(false) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -146,17 +146,9 @@ public: { return deformThicknessAdjusted() || deformWidthAdjusted(); } - bool metalnessAdjusted() const - { - return fabs(metalness - 0.0) >= 0.01; - } - bool roughnessAdjusted() const - { - return fabs(roughness - 1.0) >= 0.01; - } bool materialAdjusted() const { - return metalnessAdjusted() || roughnessAdjusted(); + return !materialId.isNull(); } bool isEditVisible() const { @@ -178,8 +170,7 @@ public: wrapped = other.wrapped; componentId = other.componentId; dirty = other.dirty; - metalness = other.metalness; - roughness = other.roughness; + materialId = other.materialId; } void updatePreviewMesh(MeshLoader *previewMesh) { @@ -411,10 +402,54 @@ private: Q_DISABLE_COPY(SkeletonMotion); }; +class SkeletonMaterialMap +{ +public: + TextureType forWhat; + QUuid imageId; +}; + +class SkeletonMaterialLayer +{ +public: + std::vector maps; +}; + +class SkeletonMaterial +{ +public: + SkeletonMaterial() + { + } + ~SkeletonMaterial() + { + delete m_previewMesh; + } + QUuid id; + QString name; + bool dirty = true; + std::vector layers; + void updatePreviewMesh(MeshLoader *previewMesh) + { + delete m_previewMesh; + m_previewMesh = previewMesh; + } + MeshLoader *takePreviewMesh() const + { + if (nullptr == m_previewMesh) + return nullptr; + return new MeshLoader(*m_previewMesh); + } +private: + Q_DISABLE_COPY(SkeletonMaterial); + MeshLoader *m_previewMesh = nullptr; +}; + enum class SkeletonDocumentToSnapshotFor { Document = 0, Nodes, + Materials, Poses, Motions }; @@ -462,8 +497,7 @@ signals: void partRoundStateChanged(QUuid partId); void partColorStateChanged(QUuid partId); void partWrapStateChanged(QUuid partId); - void partMetalnessChanged(QUuid partId); - void partRoughnessChanged(QUuid partId); + void partMaterialIdChanged(QUuid partId); void componentInverseStateChanged(QUuid partId); void cleanup(); void originChanged(); @@ -494,6 +528,15 @@ signals: void motionNameChanged(QUuid motionId); void motionControlNodesChanged(QUuid motionId); void motionKeyframesChanged(QUuid motionId); + void materialAdded(QUuid materialId); + void materialRemoved(QUuid materialId); + void materialListChanged(); + void materialNameChanged(QUuid materialId); + void materialLayersChanged(QUuid materialId); + void materialPreviewChanged(QUuid materialId); + void meshGenerating(); + void postProcessing(); + void textureGenerating(); public: // need initialize float originX; float originY; @@ -517,6 +560,8 @@ public: std::map nodeMap; std::map edgeMap; std::map componentMap; + std::map materialMap; + std::vector materialIdList; std::map poseMap; std::vector poseIdList; std::map motionMap; @@ -527,7 +572,8 @@ public: void toSnapshot(SkeletonSnapshot *snapshot, const std::set &limitNodeIds=std::set(), SkeletonDocumentToSnapshotFor forWhat=SkeletonDocumentToSnapshotFor::Document, const std::set &limitPoseIds=std::set(), - const std::set &limitMotionIds=std::set()) const; + const std::set &limitMotionIds=std::set(), + const std::set &limitMaterialIds=std::set()) const; void fromSnapshot(const SkeletonSnapshot &snapshot); void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true); const SkeletonNode *findNode(QUuid nodeId) const; @@ -537,6 +583,7 @@ public: const SkeletonComponent *findComponent(QUuid componentId) const; const SkeletonComponent *findComponentParent(QUuid componentId) const; QUuid findComponentParentId(QUuid componentId) const; + const SkeletonMaterial *findMaterial(QUuid materialId) const; const SkeletonPose *findPose(QUuid poseId) const; const SkeletonMotion *findMotion(QUuid motionId) const; MeshLoader *takeResultMesh(); @@ -547,6 +594,7 @@ public: void updateTurnaround(const QImage &image); void setSharedContextWidget(QOpenGLWidget *sharedContextWidget); bool hasPastableNodesInClipboard() const; + bool hasPastableMaterialsInClipboard() const; bool hasPastablePosesInClipboard() const; bool hasPastableMotionsInClipboard() const; bool undoable() const; @@ -592,6 +640,8 @@ public slots: void rigReady(); void generatePosePreviews(); void posePreviewsReady(); + void generateMaterialPreviews(); + void materialPreviewsReady(); void setPartLockState(QUuid partId, bool locked); void setPartVisibleState(QUuid partId, bool visible); void setPartSubdivState(QUuid partId, bool subdived); @@ -603,8 +653,7 @@ public slots: void setPartRoundState(QUuid partId, bool rounded); void setPartColorState(QUuid partId, bool hasColor, QColor color); void setPartWrapState(QUuid partId, bool wrapped); - void setPartMetalness(QUuid partId, float metalness); - void setPartRoughness(QUuid partId, float roughness); + void setPartMaterialId(QUuid partId, QUuid materialId); void setComponentInverseState(QUuid componentId, bool inverse); void moveComponentUp(QUuid componentId); void moveComponentDown(QUuid componentId); @@ -658,6 +707,10 @@ public slots: void setMotionControlNodes(QUuid motionId, std::vector controlNodes); void setMotionKeyframes(QUuid motionId, std::vector> keyframes); void renameMotion(QUuid motionId, QString name); + void addMaterial(QString name, std::vector); + void removeMaterial(QUuid materialId); + void setMaterialLayers(QUuid materialId, std::vector layers); + void renameMaterial(QUuid materialId, QString name); private: void splitPartByNode(std::vector> *groups, QUuid nodeId); void joinNodeAndNeiborsToGroup(std::vector *group, QUuid nodeId, std::set *visitMap, QUuid noUseEdgeId=QUuid()); @@ -700,6 +753,7 @@ private: // need initialize MeshResultContext *m_riggedResultContext; PosePreviewsGenerator *m_posePreviewsGenerator; bool m_currentRigSucceed; + MaterialPreviewsGenerator *m_materialPreviewsGenerator; private: static unsigned long m_maxSnapshot; std::deque m_undoItems; diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index a41941b4..9ee81d06 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -31,6 +31,9 @@ #include "rigwidget.h" #include "markiconcreator.h" #include "motionmanagewidget.h" +#include "materialmanagewidget.h" +#include "imageforever.h" +#include "spinnableawesomebutton.h" int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16; int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16; @@ -150,6 +153,22 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : QPushButton *rotateClockwiseButton = new QPushButton(QChar(fa::rotateright)); Theme::initAwesomeButton(rotateClockwiseButton); + + SpinnableAwesomeButton *regenerateButton = new SpinnableAwesomeButton(); + regenerateButton->setAwesomeIcon(QChar(fa::recycle)); + connect(m_document, &SkeletonDocument::meshGenerating, this, [=]() { + regenerateButton->showSpinner(true); + }); + connect(m_document, &SkeletonDocument::postProcessing, this, [=]() { + regenerateButton->showSpinner(true); + }); + connect(m_document, &SkeletonDocument::textureGenerating, this, [=]() { + regenerateButton->showSpinner(true); + }); + connect(m_document, &SkeletonDocument::resultTextureChanged, this, [=]() { + regenerateButton->showSpinner(false); + }); + connect(regenerateButton->button(), &QPushButton::clicked, m_document, &SkeletonDocument::regenerateMesh); toolButtonLayout->addWidget(addButton); toolButtonLayout->addWidget(selectButton); @@ -164,6 +183,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : toolButtonLayout->addSpacing(10); toolButtonLayout->addWidget(rotateCounterclockwiseButton); toolButtonLayout->addWidget(rotateClockwiseButton); + toolButtonLayout->addSpacing(10); + toolButtonLayout->addWidget(regenerateButton); + QLabel *verticalLogoLabel = new QLabel; QImage verticalLogoImage; @@ -216,6 +238,19 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : partTreeWidget->partPreviewChanged(part.first); }); + QDockWidget *materialDocker = new QDockWidget(tr("Materials"), this); + materialDocker->setAllowedAreas(Qt::RightDockWidgetArea); + MaterialManageWidget *materialManageWidget = new MaterialManageWidget(m_document, materialDocker); + materialDocker->setWidget(materialManageWidget); + connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog); + connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); + addDockWidget(Qt::RightDockWidgetArea, materialDocker); + connect(materialDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) { + Q_UNUSED(topLevel); + for (const auto &material: m_document->materialMap) + emit m_document->materialPreviewChanged(material.first); + }); + QDockWidget *rigDocker = new QDockWidget(tr("Rig"), this); rigDocker->setAllowedAreas(Qt::RightDockWidgetArea); m_rigWidget = new RigWidget(m_document, rigDocker); @@ -247,7 +282,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); addDockWidget(Qt::RightDockWidgetArea, motionDocker); - tabifyDockWidget(partTreeDocker, rigDocker); + tabifyDockWidget(partTreeDocker, materialDocker); + tabifyDockWidget(materialDocker, rigDocker); tabifyDockWidget(rigDocker, poseDocker); tabifyDockWidget(poseDocker, motionDocker); @@ -495,17 +531,17 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : }); m_viewMenu->addAction(m_resetModelWidgetPosAction); - m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this); - connect(m_toggleWireframeAction, &QAction::triggered, [=]() { - m_modelRenderWidget->toggleWireframe(); - }); - m_viewMenu->addAction(m_toggleWireframeAction); + //m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this); + //connect(m_toggleWireframeAction, &QAction::triggered, [=]() { + // m_modelRenderWidget->toggleWireframe(); + //}); + //m_viewMenu->addAction(m_toggleWireframeAction); - m_toggleSmoothNormalAction = new QAction(tr("Toggle Smooth Normal"), this); - connect(m_toggleSmoothNormalAction, &QAction::triggered, [=]() { - m_document->toggleSmoothNormal(); - }); - m_viewMenu->addAction(m_toggleSmoothNormalAction); + //m_toggleSmoothNormalAction = new QAction(tr("Toggle Smooth Normal"), this); + //connect(m_toggleSmoothNormalAction, &QAction::triggered, [=]() { + // m_document->toggleSmoothNormal(); + //}); + //m_viewMenu->addAction(m_toggleSmoothNormalAction); connect(m_viewMenu, &QMenu::aboutToShow, [=]() { m_resetModelWidgetPosAction->setEnabled(!isModelSitInVisibleArea(m_modelRenderWidget)); @@ -520,6 +556,13 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : }); m_windowMenu->addAction(m_showPartsListAction); + m_showMaterialsAction = new QAction(tr("Materials"), this); + connect(m_showMaterialsAction, &QAction::triggered, [=]() { + materialDocker->show(); + materialDocker->raise(); + }); + m_windowMenu->addAction(m_showMaterialsAction); + m_showRigAction = new QAction(tr("Rig"), this); connect(m_showRigAction, &QAction::triggered, [=]() { rigDocker->show(); @@ -760,30 +803,30 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::partRoundStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoundStateChanged); connect(m_document, &SkeletonDocument::partWrapStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partWrapStateChanged); connect(m_document, &SkeletonDocument::partColorStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partColorStateChanged); - connect(m_document, &SkeletonDocument::partMetalnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partMetalnessChanged); - connect(m_document, &SkeletonDocument::partRoughnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoughnessChanged); + connect(m_document, &SkeletonDocument::partMaterialIdChanged, partTreeWidget, &SkeletonPartTreeWidget::partMaterialIdChanged); connect(m_document, &SkeletonDocument::partRemoved, partTreeWidget, &SkeletonPartTreeWidget::partRemoved); connect(m_document, &SkeletonDocument::cleanup, partTreeWidget, &SkeletonPartTreeWidget::removeAllContent); connect(m_document, &SkeletonDocument::partChecked, partTreeWidget, &SkeletonPartTreeWidget::partChecked); connect(m_document, &SkeletonDocument::partUnchecked, partTreeWidget, &SkeletonPartTreeWidget::partUnchecked); connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh); - connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() { - if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) { - m_document->postProcess(); - } - }); + //connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() { + // if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) { + // m_document->postProcess(); + // } + //}); + connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::postProcess); connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::generateRig); connect(m_document, &SkeletonDocument::rigChanged, m_document, &SkeletonDocument::generateRig); connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateTexture); - connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture); - + //connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture); + connect(m_document, &SkeletonDocument::resultTextureChanged, [=]() { + m_modelRenderWidget->updateMesh(m_document->takeResultTextureMesh()); + }); + connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() { m_modelRenderWidget->updateMesh(m_document->takeResultMesh()); }); - //connect(m_document, &SkeletonDocument::resultSkeletonChanged, [=]() { - // m_skeletonRenderWidget->updateMesh(m_document->takeResultSkeletonMesh()); - //}); connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() { m_modelRenderWidget->setCursor(graphicsWidget->cursor()); @@ -821,6 +864,15 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_document->generatePosePreviews(); }); connect(m_document, &SkeletonDocument::resultRigChanged, m_document, &SkeletonDocument::generatePosePreviews); + + connect(m_document, &SkeletonDocument::materialAdded, this, [=](QUuid materialId) { + Q_UNUSED(materialId); + m_document->generateMaterialPreviews(); + }); + connect(m_document, &SkeletonDocument::materialLayersChanged, this, [=](QUuid materialId) { + Q_UNUSED(materialId); + m_document->generateMaterialPreviews(); + }); connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady); @@ -1037,6 +1089,26 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename) if (imageByteArray.size() > 0) ds3Writer.add("canvas.png", "asset", &imageByteArray); } + + for (auto &material: snapshot.materials) { + for (auto &layer: material.second) { + for (auto &mapItem: layer.second) { + auto findImageIdString = mapItem.find("linkData"); + if (findImageIdString == mapItem.end()) + continue; + QUuid imageId = QUuid(findImageIdString->second); + const QImage *image = ImageForever::get(imageId); + if (nullptr == image) + continue; + QByteArray imageByteArray; + QBuffer pngBuffer(&imageByteArray); + pngBuffer.open(QIODevice::WriteOnly); + image->save(&pngBuffer, "PNG"); + if (imageByteArray.size() > 0) + ds3Writer.add("images/" + imageId.toString() + ".png", "asset", &imageByteArray); + } + } + } if (ds3Writer.save(filename)) { setCurrentFilename(filename); @@ -1063,6 +1135,24 @@ void SkeletonDocumentWindow::open() QApplication::setOverrideCursor(Qt::WaitCursor); Ds3FileReader ds3Reader(filename); + + for (int i = 0; i < ds3Reader.items().size(); ++i) { + Ds3ReaderItem item = ds3Reader.items().at(i); + if (item.type == "asset") { + if (item.name.startsWith("images/")) { + QString filename = item.name.split("/")[1]; + QString imageIdString = filename.split(".")[0]; + QUuid imageId = QUuid(imageIdString); + if (!imageId.isNull()) { + QByteArray data; + ds3Reader.loadItem(item.name, &data); + QImage image = QImage::fromData(data, "PNG"); + (void)ImageForever::add(&image, imageId); + } + } + } + } + for (int i = 0; i < ds3Reader.items().size(); ++i) { Ds3ReaderItem item = ds3Reader.items().at(i); if (item.type == "model") { @@ -1104,19 +1194,11 @@ void SkeletonDocumentWindow::exportObjResult() return; } QApplication::setOverrideCursor(Qt::WaitCursor); - m_modelRenderWidget->exportMeshAsObj(filename); - QApplication::restoreOverrideCursor(); -} - -void SkeletonDocumentWindow::exportObjPlusMaterialsResult() -{ - QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), - tr("Wavefront (*.obj)")); - if (filename.isEmpty()) { - return; + MeshLoader *resultMesh = m_document->takeResultMesh(); + if (nullptr != resultMesh) { + resultMesh->exportAsObj(filename); + delete resultMesh; } - QApplication::setOverrideCursor(Qt::WaitCursor); - m_modelRenderWidget->exportMeshAsObjPlusMaterials(filename); QApplication::restoreOverrideCursor(); } diff --git a/src/skeletondocumentwindow.h b/src/skeletondocumentwindow.h index b0b8d8e2..431fbfe6 100644 --- a/src/skeletondocumentwindow.h +++ b/src/skeletondocumentwindow.h @@ -39,7 +39,6 @@ public slots: void saveTo(const QString &saveAsFilename); void open(); void exportObjResult(); - void exportObjPlusMaterialsResult(); void exportGltfResult(); void showExportPreview(); void newWindow(); @@ -135,6 +134,7 @@ private: QMenu *m_windowMenu; QAction *m_showPartsListAction; QAction *m_showDebugDialogAction; + QAction *m_showMaterialsAction; QAction *m_showRigAction; QAction *m_showPosesAction; QAction *m_showMotionsAction; diff --git a/src/skeletonparttreewidget.cpp b/src/skeletonparttreewidget.cpp index 05977b15..df759402 100644 --- a/src/skeletonparttreewidget.cpp +++ b/src/skeletonparttreewidget.cpp @@ -932,18 +932,7 @@ void SkeletonPartTreeWidget::partColorStateChanged(QUuid partId) widget->updateColorButton(); } -void SkeletonPartTreeWidget::partMetalnessChanged(QUuid partId) -{ - auto item = m_partItemMap.find(partId); - if (item == m_partItemMap.end()) { - qDebug() << "Part item not found:" << partId; - return; - } - SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second, 0); - widget->updateColorButton(); -} - -void SkeletonPartTreeWidget::partRoughnessChanged(QUuid partId) +void SkeletonPartTreeWidget::partMaterialIdChanged(QUuid partId) { auto item = m_partItemMap.find(partId); if (item == m_partItemMap.end()) { diff --git a/src/skeletonparttreewidget.h b/src/skeletonparttreewidget.h index 73d7736f..c1396865 100644 --- a/src/skeletonparttreewidget.h +++ b/src/skeletonparttreewidget.h @@ -62,8 +62,7 @@ public slots: void partRoundStateChanged(QUuid partId); void partWrapStateChanged(QUuid partId); void partColorStateChanged(QUuid partId); - void partMetalnessChanged(QUuid partId); - void partRoughnessChanged(QUuid partId); + void partMaterialIdChanged(QUuid partId); void partChecked(QUuid partId); void partUnchecked(QUuid partId); void groupChanged(QTreeWidgetItem *item, int column); diff --git a/src/skeletonpartwidget.cpp b/src/skeletonpartwidget.cpp index 8a2f7319..9c8a1322 100644 --- a/src/skeletonpartwidget.cpp +++ b/src/skeletonpartwidget.cpp @@ -5,9 +5,12 @@ #include #include #include +#include #include "skeletonpartwidget.h" #include "theme.h" #include "floatnumberwidget.h" +#include "materiallistwidget.h" +#include "infolabel.h" SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid partId) : m_document(document), @@ -131,8 +134,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p connect(this, &SkeletonPartWidget::setPartRoundState, m_document, &SkeletonDocument::setPartRoundState); connect(this, &SkeletonPartWidget::setPartWrapState, m_document, &SkeletonDocument::setPartWrapState); connect(this, &SkeletonPartWidget::setPartColorState, m_document, &SkeletonDocument::setPartColorState); - connect(this, &SkeletonPartWidget::setPartMetalness, m_document, &SkeletonDocument::setPartMetalness); - connect(this, &SkeletonPartWidget::setPartRoughness, m_document, &SkeletonDocument::setPartRoughness); + connect(this, &SkeletonPartWidget::setPartMaterialId, m_document, &SkeletonDocument::setPartMaterialId); connect(this, &SkeletonPartWidget::checkPart, m_document, &SkeletonDocument::checkPart); connect(this, &SkeletonPartWidget::enableBackgroundBlur, m_document, &SkeletonDocument::enableBackgroundBlur); connect(this, &SkeletonPartWidget::disableBackgroundBlur, m_document, &SkeletonDocument::disableBackgroundBlur); @@ -323,53 +325,23 @@ void SkeletonPartWidget::showColorSettingPopup(const QPoint &pos) } }); - FloatNumberWidget *metalnessWidget = new FloatNumberWidget; - metalnessWidget->setItemName(tr("Metalness")); - metalnessWidget->setRange(0, 1); - metalnessWidget->setValue(part->metalness); - - connect(metalnessWidget, &FloatNumberWidget::valueChanged, [=](float value) { - emit setPartMetalness(m_partId, value); - emit groupOperationAdded(); - }); - - FloatNumberWidget *roughnessWidget = new FloatNumberWidget; - roughnessWidget->setItemName(tr("Roughness")); - roughnessWidget->setRange(0, 1); - roughnessWidget->setValue(part->roughness); - - connect(roughnessWidget, &FloatNumberWidget::valueChanged, [=](float value) { - emit setPartRoughness(m_partId, value); - emit groupOperationAdded(); - }); - - QPushButton *metalnessEraser = new QPushButton(QChar(fa::eraser)); - initToolButton(metalnessEraser); - - connect(metalnessEraser, &QPushButton::clicked, [=]() { - metalnessWidget->setValue(0.0); - emit groupOperationAdded(); - }); - - QPushButton *roughnessEraser = new QPushButton(QChar(fa::eraser)); - initToolButton(roughnessEraser); - - connect(roughnessEraser, &QPushButton::clicked, [=]() { - roughnessWidget->setValue(1.0); - emit groupOperationAdded(); - }); - - QHBoxLayout *metalnessLayout = new QHBoxLayout; - QHBoxLayout *roughnessLayout = new QHBoxLayout; - metalnessLayout->addWidget(metalnessEraser); - metalnessLayout->addWidget(metalnessWidget); - roughnessLayout->addWidget(roughnessEraser); - roughnessLayout->addWidget(roughnessWidget); - QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(colorLayout); - mainLayout->addLayout(metalnessLayout); - mainLayout->addLayout(roughnessLayout); + + if (m_document->materialIdList.empty()) { + InfoLabel *infoLabel = new InfoLabel; + infoLabel->setText(tr("Missing Materials")); + mainLayout->addWidget(infoLabel); + } else { + MaterialListWidget *materialListWidget = new MaterialListWidget(m_document); + materialListWidget->enableMultipleSelection(false); + materialListWidget->selectMaterial(part->materialId); + connect(materialListWidget, &MaterialListWidget::currentSelectedMaterialChanged, this, [=](QUuid materialId) { + emit setPartMaterialId(m_partId, materialId); + emit groupOperationAdded(); + }); + mainLayout->addWidget(materialListWidget); + } popup->setLayout(mainLayout); diff --git a/src/skeletonpartwidget.h b/src/skeletonpartwidget.h index f227c3cd..d5d2c63e 100644 --- a/src/skeletonpartwidget.h +++ b/src/skeletonpartwidget.h @@ -21,8 +21,7 @@ signals: void setPartRoundState(QUuid partId, bool rounded); void setPartColorState(QUuid partId, bool hasColor, QColor color); void setPartWrapState(QUuid partId, bool wrapped); - void setPartMetalness(QUuid partId, float metalness); - void setPartRoughness(QUuid partId, float roughness); + void setPartMaterialId(QUuid partId, QUuid materialId); void movePartUp(QUuid partId); void movePartDown(QUuid partId); void movePartToTop(QUuid partId); diff --git a/src/skeletonsnapshot.h b/src/skeletonsnapshot.h index a6154b2d..32494648 100644 --- a/src/skeletonsnapshot.h +++ b/src/skeletonsnapshot.h @@ -17,6 +17,7 @@ public: std::map rootComponent; std::vector, std::map>>> poses; // std::pair std::vector, std::vector>, std::vector>>> motions; // std::tuple + std::vector, std::vector, std::vector>>>>> materials; // std::pair layer: std::pair public: void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString()); }; diff --git a/src/skeletonxml.cpp b/src/skeletonxml.cpp index 7a943933..7005c8ed 100644 --- a/src/skeletonxml.cpp +++ b/src/skeletonxml.cpp @@ -91,6 +91,41 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write writer->writeEndElement(); } + writer->writeStartElement("materials"); + std::vector, std::vector, std::vector>>>>>::iterator materialIterator; + for (materialIterator = snapshot->materials.begin(); materialIterator != snapshot->materials.end(); materialIterator++) { + std::map::iterator materialAttributeIterator; + writer->writeStartElement("material"); + for (materialAttributeIterator = materialIterator->first.begin(); materialAttributeIterator != materialIterator->first.end(); materialAttributeIterator++) { + writer->writeAttribute(materialAttributeIterator->first, materialAttributeIterator->second); + } + writer->writeStartElement("layers"); + std::vector, std::vector>>>::iterator layerIterator; + for (layerIterator = materialIterator->second.begin(); layerIterator != materialIterator->second.end(); layerIterator++) { + std::map::iterator layerAttributeIterator; + writer->writeStartElement("layer"); + for (layerAttributeIterator = layerIterator->first.begin(); layerAttributeIterator != layerIterator->first.end(); layerAttributeIterator++) { + writer->writeAttribute(layerAttributeIterator->first, layerAttributeIterator->second); + } + writer->writeStartElement("maps"); + std::vector>::iterator mapIterator; + for (mapIterator = layerIterator->second.begin(); mapIterator != layerIterator->second.end(); mapIterator++) { + std::map::iterator attributesIterator; + writer->writeStartElement("map"); + for (attributesIterator = mapIterator->begin(); attributesIterator != mapIterator->end(); + attributesIterator++) { + writer->writeAttribute(attributesIterator->first, attributesIterator->second); + } + writer->writeEndElement(); + } + writer->writeEndElement(); + writer->writeEndElement(); + } + writer->writeEndElement(); + writer->writeEndElement(); + } + writer->writeEndElement(); + writer->writeStartElement("poses"); std::vector, std::map>>>::iterator poseIterator; for (poseIterator = snapshot->poses.begin(); poseIterator != snapshot->poses.end(); poseIterator++) { @@ -165,6 +200,8 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea { std::stack componentStack; std::vector elementNameStack; + std::pair, std::vector>> currentMaterialLayer; + std::pair, std::vector, std::vector>>>> currentMaterial; std::pair, std::map>> currentPose; std::tuple, std::vector>, std::vector>> currentMotion; while (!reader.atEnd()) { @@ -243,6 +280,25 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea if (!parentChildrenIds.isEmpty()) parentChildrenIds += ","; parentChildrenIds += componentId; + } else if (fullName == "canvas.materials.material.layers.layer") { + currentMaterialLayer = decltype(currentMaterialLayer)(); + foreach(const QXmlStreamAttribute &attr, reader.attributes()) { + currentMaterialLayer.first[attr.name().toString()] = attr.value().toString(); + } + } else if (fullName == "canvas.materials.material.layers.layer.maps.map") { + std::map attributes; + foreach(const QXmlStreamAttribute &attr, reader.attributes()) { + attributes[attr.name().toString()] = attr.value().toString(); + } + currentMaterialLayer.second.push_back(attributes); + } else if (fullName == "canvas.materials.material") { + QString materialId = reader.attributes().value("id").toString(); + if (materialId.isEmpty()) + continue; + currentMaterial = decltype(currentMaterial)(); + foreach(const QXmlStreamAttribute &attr, reader.attributes()) { + currentMaterial.first[attr.name().toString()] = attr.value().toString(); + } } else if (fullName == "canvas.poses.pose") { QString poseId = reader.attributes().value("id").toString(); if (poseId.isEmpty()) @@ -285,6 +341,10 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea } else if (reader.isEndElement()) { if (fullName.startsWith("canvas.components.component")) { componentStack.pop(); + } else if (fullName == "canvas.materials.material.layers.layer") { + currentMaterial.second.push_back(currentMaterialLayer); + } else if (fullName == "canvas.materials.material") { + snapshot->materials.push_back(currentMaterial); } else if (fullName == "canvas.poses.pose") { snapshot->poses.push_back(currentPose); } else if (fullName == "canvas.motions.motion") { diff --git a/src/skinnedmeshcreator.cpp b/src/skinnedmeshcreator.cpp index a38555cb..f7eb0566 100644 --- a/src/skinnedmeshcreator.cpp +++ b/src/skinnedmeshcreator.cpp @@ -62,8 +62,8 @@ MeshLoader *SkinnedMeshCreator::createMeshFromTransform(const std::vector #include +#include +#include #include "texturegenerator.h" #include "theme.h" -int TextureGenerator::m_textureWidth = 512; -int TextureGenerator::m_textureHeight = 512; +int TextureGenerator::m_textureSize = 1024; -TextureGenerator::TextureGenerator(const MeshResultContext &meshResultContext, QThread *thread) : +TextureGenerator::TextureGenerator(const MeshResultContext &meshResultContext) : m_resultTextureGuideImage(nullptr), m_resultTextureImage(nullptr), m_resultTextureBorderImage(nullptr), m_resultTextureColorImage(nullptr), - m_thread(thread), + m_resultTextureNormalImage(nullptr), + m_resultTextureMetalnessRoughnessAmbientOcclusionImage(nullptr), + m_resultTextureRoughnessImage(nullptr), + m_resultTextureMetalnessImage(nullptr), + m_resultTextureAmbientOcclusionImage(nullptr), m_resultMesh(nullptr) { m_resultContext = new MeshResultContext(); @@ -25,6 +30,11 @@ TextureGenerator::~TextureGenerator() delete m_resultTextureImage; delete m_resultTextureBorderImage; delete m_resultTextureColorImage; + delete m_resultTextureNormalImage; + delete m_resultTextureMetalnessRoughnessAmbientOcclusionImage; + delete m_resultTextureRoughnessImage; + delete m_resultTextureMetalnessImage; + delete m_resultTextureAmbientOcclusionImage; delete m_resultMesh; } @@ -56,6 +66,13 @@ QImage *TextureGenerator::takeResultTextureColorImage() return resultTextureColorImage; } +QImage *TextureGenerator::takeResultTextureNormalImage() +{ + QImage *resultTextureNormalImage = m_resultTextureNormalImage; + m_resultTextureNormalImage = nullptr; + return resultTextureNormalImage; +} + MeshResultContext *TextureGenerator::takeResultContext() { MeshResultContext *resultContext = m_resultContext; @@ -70,17 +87,80 @@ MeshLoader *TextureGenerator::takeResultMesh() return resultMesh; } -void TextureGenerator::process() +void TextureGenerator::addPartColorMap(QUuid partId, const QImage *image) { + if (nullptr == image) + return; + m_partColorTextureMap[partId] = *image; +} + +void TextureGenerator::addPartNormalMap(QUuid partId, const QImage *image) +{ + if (nullptr == image) + return; + m_partNormalTextureMap[partId] = *image; +} + +void TextureGenerator::addPartMetalnessMap(QUuid partId, const QImage *image) +{ + if (nullptr == image) + return; + m_partMetalnessTextureMap[partId] = *image; +} + +void TextureGenerator::addPartRoughnessMap(QUuid partId, const QImage *image) +{ + if (nullptr == image) + return; + m_partRoughnessTextureMap[partId] = *image; +} + +void TextureGenerator::addPartAmbientOcclusionMap(QUuid partId, const QImage *image) +{ + if (nullptr == image) + return; + m_partAmbientOcclusionTextureMap[partId] = *image; +} + +QPainterPath TextureGenerator::expandedPainterPath(const QPainterPath &painterPath) +{ + QPainterPathStroker stroker; + stroker.setWidth(20); + stroker.setJoinStyle(Qt::MiterJoin); + return (stroker.createStroke(painterPath) + painterPath).simplified(); +} + +void TextureGenerator::generate() +{ + bool hasNormalMap = false; + bool hasMetalnessMap = false; + bool hasRoughnessMap = false; + bool hasAmbientOcclusionMap = false; + const std::vector &triangleMaterials = m_resultContext->triangleMaterials(); const std::vector &triangleUvs = m_resultContext->triangleUvs(); - m_resultTextureColorImage = new QImage(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight, QImage::Format_ARGB32); - m_resultTextureColorImage->fill(Qt::transparent); + m_resultTextureColorImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32); + m_resultTextureColorImage->fill(Theme::white); - m_resultTextureBorderImage = new QImage(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight, QImage::Format_ARGB32); + m_resultTextureBorderImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32); m_resultTextureBorderImage->fill(Qt::transparent); + m_resultTextureNormalImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32); + m_resultTextureNormalImage->fill(Qt::transparent); + + m_resultTextureMetalnessRoughnessAmbientOcclusionImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32); + m_resultTextureMetalnessRoughnessAmbientOcclusionImage->fill(Qt::transparent); + + m_resultTextureMetalnessImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32); + m_resultTextureMetalnessImage->fill(Qt::transparent); + + m_resultTextureRoughnessImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32); + m_resultTextureRoughnessImage->fill(Qt::transparent); + + m_resultTextureAmbientOcclusionImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32); + m_resultTextureAmbientOcclusionImage->fill(Qt::transparent); + QColor borderColor = Qt::darkGray; QPen pen(borderColor); @@ -93,6 +173,26 @@ void TextureGenerator::process() textureBorderPainter.begin(m_resultTextureBorderImage); textureBorderPainter.setRenderHint(QPainter::Antialiasing); textureBorderPainter.setRenderHint(QPainter::HighQualityAntialiasing); + + QPainter textureNormalPainter; + textureNormalPainter.begin(m_resultTextureNormalImage); + textureNormalPainter.setRenderHint(QPainter::Antialiasing); + textureNormalPainter.setRenderHint(QPainter::HighQualityAntialiasing); + + QPainter textureMetalnessPainter; + textureMetalnessPainter.begin(m_resultTextureMetalnessImage); + textureMetalnessPainter.setRenderHint(QPainter::Antialiasing); + textureMetalnessPainter.setRenderHint(QPainter::HighQualityAntialiasing); + + QPainter textureRoughnessPainter; + textureRoughnessPainter.begin(m_resultTextureRoughnessImage); + textureRoughnessPainter.setRenderHint(QPainter::Antialiasing); + textureRoughnessPainter.setRenderHint(QPainter::HighQualityAntialiasing); + + QPainter textureAmbientOcclusionPainter; + textureAmbientOcclusionPainter.begin(m_resultTextureAmbientOcclusionImage); + textureAmbientOcclusionPainter.setRenderHint(QPainter::Antialiasing); + textureAmbientOcclusionPainter.setRenderHint(QPainter::HighQualityAntialiasing); // round 1, paint background for (auto i = 0u; i < triangleUvs.size(); i++) { @@ -100,11 +200,12 @@ void TextureGenerator::process() const ResultTriangleUv *uv = &triangleUvs[i]; for (auto j = 0; j < 3; j++) { if (0 == j) { - path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); + path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureSize, uv->uv[j][1] * TextureGenerator::m_textureSize); } else { - path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); + path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureSize, uv->uv[j][1] * TextureGenerator::m_textureSize); } } + path = expandedPainterPath(path); QPen textureBorderPen(triangleMaterials[i].color); textureBorderPen.setWidth(32); texturePainter.setPen(textureBorderPen); @@ -116,14 +217,82 @@ void TextureGenerator::process() for (auto i = 0u; i < triangleUvs.size(); i++) { QPainterPath path; const ResultTriangleUv *uv = &triangleUvs[i]; - for (auto j = 0; j < 3; j++) { - if (0 == j) { - path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); - } else { - path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); - } - } + float points[][2] = { + {uv->uv[0][0] * TextureGenerator::m_textureSize, uv->uv[0][1] * TextureGenerator::m_textureSize}, + {uv->uv[1][0] * TextureGenerator::m_textureSize, uv->uv[1][1] * TextureGenerator::m_textureSize}, + {uv->uv[2][0] * TextureGenerator::m_textureSize, uv->uv[2][1] * TextureGenerator::m_textureSize} + }; + path.moveTo(points[0][0], points[0][1]); + path.lineTo(points[1][0], points[1][1]); + path.lineTo(points[2][0], points[2][1]); + path = expandedPainterPath(path); + // Fill base color texturePainter.fillPath(path, QBrush(triangleMaterials[i].color)); + // Copy color texture if there is one + const std::pair source = m_resultContext->triangleSourceNodes()[i]; + auto findColorTextureResult = m_partColorTextureMap.find(source.first); + if (findColorTextureResult != m_partColorTextureMap.end()) { + texturePainter.setClipping(true); + texturePainter.setClipPath(path); + QPen textureBorderPen(triangleMaterials[i].color); + textureBorderPen.setWidth(0); + texturePainter.setPen(textureBorderPen); + texturePainter.drawImage(0, 0, findColorTextureResult->second); + texturePainter.setPen(Qt::NoPen); + texturePainter.setClipping(false); + } + // Copy normal texture if there is one + auto findNormalTextureResult = m_partNormalTextureMap.find(source.first); + if (findNormalTextureResult != m_partNormalTextureMap.end()) { + textureNormalPainter.setClipping(true); + textureNormalPainter.setClipPath(path); + QPen textureBorderPen(triangleMaterials[i].color); + textureBorderPen.setWidth(0); + textureNormalPainter.setPen(textureBorderPen); + textureNormalPainter.drawImage(0, 0, findNormalTextureResult->second); + textureNormalPainter.setPen(Qt::NoPen); + textureNormalPainter.setClipping(false); + hasNormalMap = true; + } + // Copy metalness texture if there is one + auto findMetalnessTextureResult = m_partMetalnessTextureMap.find(source.first); + if (findMetalnessTextureResult != m_partMetalnessTextureMap.end()) { + textureMetalnessPainter.setClipping(true); + textureMetalnessPainter.setClipPath(path); + QPen textureBorderPen(triangleMaterials[i].color); + textureBorderPen.setWidth(0); + textureMetalnessPainter.setPen(textureBorderPen); + textureMetalnessPainter.drawImage(0, 0, findMetalnessTextureResult->second); + textureMetalnessPainter.setPen(Qt::NoPen); + textureMetalnessPainter.setClipping(false); + hasMetalnessMap = true; + } + // Copy roughness texture if there is one + auto findRoughnessTextureResult = m_partRoughnessTextureMap.find(source.first); + if (findRoughnessTextureResult != m_partRoughnessTextureMap.end()) { + textureRoughnessPainter.setClipping(true); + textureRoughnessPainter.setClipPath(path); + QPen textureBorderPen(triangleMaterials[i].color); + textureBorderPen.setWidth(0); + textureRoughnessPainter.setPen(textureBorderPen); + textureRoughnessPainter.drawImage(0, 0, findRoughnessTextureResult->second); + textureRoughnessPainter.setPen(Qt::NoPen); + textureRoughnessPainter.setClipping(false); + hasRoughnessMap = true; + } + // Copy ambient occlusion texture if there is one + auto findAmbientOcclusionTextureResult = m_partAmbientOcclusionTextureMap.find(source.first); + if (findAmbientOcclusionTextureResult != m_partAmbientOcclusionTextureMap.end()) { + textureAmbientOcclusionPainter.setClipping(true); + textureAmbientOcclusionPainter.setClipPath(path); + QPen textureBorderPen(triangleMaterials[i].color); + textureBorderPen.setWidth(0); + textureAmbientOcclusionPainter.setPen(textureBorderPen); + textureAmbientOcclusionPainter.drawImage(0, 0, findAmbientOcclusionTextureResult->second); + textureAmbientOcclusionPainter.setPen(Qt::NoPen); + textureAmbientOcclusionPainter.setClipping(false); + hasAmbientOcclusionMap = true; + } } pen.setWidth(0); @@ -133,13 +302,40 @@ void TextureGenerator::process() for (auto j = 0; j < 3; j++) { int from = j; int to = (j + 1) % 3; - textureBorderPainter.drawLine(uv->uv[from][0] * TextureGenerator::m_textureWidth, uv->uv[from][1] * TextureGenerator::m_textureHeight, - uv->uv[to][0] * TextureGenerator::m_textureWidth, uv->uv[to][1] * TextureGenerator::m_textureHeight); + textureBorderPainter.drawLine(uv->uv[from][0] * TextureGenerator::m_textureSize, uv->uv[from][1] * TextureGenerator::m_textureSize, + uv->uv[to][0] * TextureGenerator::m_textureSize, uv->uv[to][1] * TextureGenerator::m_textureSize); } } texturePainter.end(); textureBorderPainter.end(); + textureNormalPainter.end(); + textureMetalnessPainter.end(); + textureRoughnessPainter.end(); + textureAmbientOcclusionPainter.end(); + + if (!hasNormalMap) { + delete m_resultTextureNormalImage; + m_resultTextureNormalImage = nullptr; + } + + if (!hasMetalnessMap && !hasRoughnessMap && !hasAmbientOcclusionMap) { + delete m_resultTextureMetalnessRoughnessAmbientOcclusionImage; + m_resultTextureMetalnessRoughnessAmbientOcclusionImage = nullptr; + } else { + for (int row = 0; row < m_resultTextureMetalnessRoughnessAmbientOcclusionImage->height(); ++row) { + for (int col = 0; col < m_resultTextureMetalnessRoughnessAmbientOcclusionImage->width(); ++col) { + QColor color; + if (hasMetalnessMap) + color.setBlue(qGray(m_resultTextureMetalnessImage->pixel(col, row))); + if (hasRoughnessMap) + color.setRed(qGray(m_resultTextureRoughnessImage->pixel(col, row))); + if (hasAmbientOcclusionMap) + color.setGreen(qGray(m_resultTextureAmbientOcclusionImage->pixel(col, row))); + m_resultTextureMetalnessRoughnessAmbientOcclusionImage->setPixelColor(col, row, color); + } + } + } m_resultTextureImage = new QImage(*m_resultTextureColorImage); @@ -151,8 +347,20 @@ void TextureGenerator::process() m_resultMesh = new MeshLoader(*m_resultContext); m_resultMesh->setTextureImage(new QImage(*m_resultTextureImage)); - + if (nullptr != m_resultTextureNormalImage) + m_resultMesh->setNormalMapImage(new QImage(*m_resultTextureNormalImage)); + if (nullptr != m_resultTextureMetalnessRoughnessAmbientOcclusionImage) { + m_resultMesh->setMetalnessRoughnessAmbientOcclusionImage(new QImage(*m_resultTextureMetalnessRoughnessAmbientOcclusionImage)); + m_resultMesh->setHasMetalnessInImage(hasMetalnessMap); + m_resultMesh->setHasRoughnessInImage(hasRoughnessMap); + m_resultMesh->setHasAmbientOcclusionInImage(hasAmbientOcclusionMap); + } +} + +void TextureGenerator::process() +{ + generate(); + this->moveToThread(QGuiApplication::instance()->thread()); - emit finished(); } diff --git a/src/texturegenerator.h b/src/texturegenerator.h index 922aa58d..811f859e 100644 --- a/src/texturegenerator.h +++ b/src/texturegenerator.h @@ -10,29 +10,46 @@ class TextureGenerator : public QObject { Q_OBJECT public: - TextureGenerator(const MeshResultContext &meshResultContext, QThread *thread); + TextureGenerator(const MeshResultContext &meshResultContext); ~TextureGenerator(); QImage *takeResultTextureGuideImage(); QImage *takeResultTextureImage(); QImage *takeResultTextureBorderImage(); QImage *takeResultTextureColorImage(); + QImage *takeResultTextureNormalImage(); MeshResultContext *takeResultContext(); MeshLoader *takeResultMesh(); + void addPartColorMap(QUuid partId, const QImage *image); + void addPartNormalMap(QUuid partId, const QImage *image); + void addPartMetalnessMap(QUuid partId, const QImage *image); + void addPartRoughnessMap(QUuid partId, const QImage *image); + void addPartAmbientOcclusionMap(QUuid partId, const QImage *image); + void generate(); signals: void finished(); public slots: void process(); public: - static int m_textureWidth; - static int m_textureHeight; + static int m_textureSize; +private: + QPainterPath expandedPainterPath(const QPainterPath &painterPath); private: MeshResultContext *m_resultContext; QImage *m_resultTextureGuideImage; QImage *m_resultTextureImage; QImage *m_resultTextureBorderImage; QImage *m_resultTextureColorImage; - QThread *m_thread; + QImage *m_resultTextureNormalImage; + QImage *m_resultTextureMetalnessRoughnessAmbientOcclusionImage; + QImage *m_resultTextureRoughnessImage; + QImage *m_resultTextureMetalnessImage; + QImage *m_resultTextureAmbientOcclusionImage; MeshLoader *m_resultMesh; + std::map m_partColorTextureMap; + std::map m_partNormalTextureMap; + std::map m_partMetalnessTextureMap; + std::map m_partRoughnessTextureMap; + std::map m_partAmbientOcclusionTextureMap; }; #endif diff --git a/src/texturetype.cpp b/src/texturetype.cpp new file mode 100644 index 00000000..7cbaf6ac --- /dev/null +++ b/src/texturetype.cpp @@ -0,0 +1,5 @@ +#include "texturetype.h" + +IMPL_TextureTypeToDispName +IMPL_TextureTypeToString +IMPL_TextureTypeFromString \ No newline at end of file diff --git a/src/texturetype.h b/src/texturetype.h new file mode 100644 index 00000000..1883cafd --- /dev/null +++ b/src/texturetype.h @@ -0,0 +1,79 @@ +#ifndef TEXTURE_TYPE_H +#define TEXTURE_TYPE_H +#include +#include +#include + +enum class TextureType +{ + None, + BaseColor, + Normal, + Metalness, + Roughness, + AmbientOcclusion, + Count +}; + +QString TextureTypeToDispName(TextureType type); +#define IMPL_TextureTypeToDispName \ +QString TextureTypeToDispName(TextureType type) \ +{ \ + switch (type) { \ + case TextureType::BaseColor: \ + return QObject::tr("Base Color"); \ + case TextureType::Normal: \ + return QObject::tr("Normal"); \ + case TextureType::Metalness: \ + return QObject::tr("Metalness"); \ + case TextureType::Roughness: \ + return QObject::tr("Roughness"); \ + case TextureType::AmbientOcclusion: \ + return QObject::tr("Ambient Occlusion"); \ + case TextureType::None: \ + return QObject::tr("None"); \ + default: \ + return ""; \ + } \ +} + +const char *TextureTypeToString(TextureType type); +#define IMPL_TextureTypeToString \ +const char *TextureTypeToString(TextureType type) \ +{ \ + switch (type) { \ + case TextureType::BaseColor: \ + return "BaseColor"; \ + case TextureType::Normal: \ + return "Normal"; \ + case TextureType::Metalness: \ + return "Metalness"; \ + case TextureType::Roughness: \ + return "Roughness"; \ + case TextureType::AmbientOcclusion: \ + return "AmbientOcclusion"; \ + case TextureType::None: \ + return "None"; \ + default: \ + return ""; \ + } \ +} +TextureType TextureTypeFromString(const char *typeString); +#define IMPL_TextureTypeFromString \ +TextureType TextureTypeFromString(const char *typeString) \ +{ \ + QString type = typeString; \ + if (type == "BaseColor") \ + return TextureType::BaseColor; \ + if (type == "Normal") \ + return TextureType::Normal; \ + if (type == "Metalness") \ + return TextureType::Metalness; \ + if (type == "Roughness") \ + return TextureType::Roughness; \ + if (type == "AmbientOcclusion") \ + return TextureType::AmbientOcclusion; \ + return TextureType::None; \ +} + +#endif diff --git a/src/theme.cpp b/src/theme.cpp index b09c80d0..dd22d024 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -34,6 +34,7 @@ int Theme::toolIconSize = 24; int Theme::miniIconFontSize = 9; int Theme::miniIconSize = 15; int Theme::partPreviewImageSize = (Theme::miniIconSize * 3); +int Theme::materialPreviewImageSize = 75; int Theme::posePreviewImageSize = 75; int Theme::motionPreviewImageSize = 75; int Theme::sidebarPreferredWidth = 200; diff --git a/src/theme.h b/src/theme.h index b9e0f810..d6548569 100644 --- a/src/theme.h +++ b/src/theme.h @@ -34,6 +34,7 @@ public: static QWidget *createVerticalLineWidget(); static int toolIconFontSize; static int toolIconSize; + static int materialPreviewImageSize; static int posePreviewImageSize; static int partPreviewImageSize; static int motionPreviewImageSize; diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite_ffi.dll b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite_ffi.dll index 837cdd81..8c187716 100644 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite_ffi.dll and b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite_ffi.dll differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite_ffi.dll.lib b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite_ffi.dll.lib index edaefe0e..c03dbd4b 100644 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite_ffi.dll.lib and b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite_ffi.dll.lib differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite_ffi.dll b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite_ffi.dll index 9d80ecdb..a86d9076 100644 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite_ffi.dll and b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite_ffi.dll differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite_ffi.dll.lib b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite_ffi.dll.lib index 3e1df2a9..d640e890 100644 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite_ffi.dll.lib and b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite_ffi.dll.lib differ