Add MetalicRoughness material manager

Besides adding the PBR material manager, this commit hide some unnecessary features such as toggle wireframes and toggle normal smooth.
Add a regenerate button to indicate mesh is generating by showing a spiner, user can click this button to regenerate if the final result is not good.
Please note that, we generate the texture map by default compared to before we only generate texture map when user export glTF result. Current uvunwrap library cann't handle bad mesh very well, so it will crash on the wires model.
master
Jeremy Hu 2018-10-09 10:19:12 +08:00
parent 308bc48c54
commit 7eb5a325a0
54 changed files with 2498 additions and 313 deletions

View File

@ -224,6 +224,27 @@ HEADERS += src/motionpreviewsgenerator.h
SOURCES += src/animationclipplayer.cpp SOURCES += src/animationclipplayer.cpp
HEADERS += src/animationclipplayer.h 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 SOURCES += src/main.cpp
HEADERS += src/version.h HEADERS += src/version.h

View File

@ -6,6 +6,7 @@
<file>resources/tree-branch-more.png</file> <file>resources/tree-branch-more.png</file>
<file>resources/tree-branch-open.png</file> <file>resources/tree-branch-open.png</file>
<file>resources/tree-vline.png</file> <file>resources/tree-vline.png</file>
<file>resources/material-demo-model.ds3</file>
<file>shaders/default.vert</file> <file>shaders/default.vert</file>
<file>shaders/default.frag</file> <file>shaders/default.frag</file>
<file>shaders/default.core.vert</file> <file>shaders/default.core.vert</file>

Binary file not shown.

View File

@ -5,6 +5,7 @@ in vec3 color;
in vec2 texCoord; in vec2 texCoord;
in float metalness; in float metalness;
in float roughness; in float roughness;
in vec3 tangent;
out vec3 vert; out vec3 vert;
out vec3 vertNormal; out vec3 vertNormal;
out vec3 vertColor; out vec3 vertColor;

View File

@ -4,6 +4,7 @@ attribute vec3 color;
attribute vec2 texCoord; attribute vec2 texCoord;
attribute float metalness; attribute float metalness;
attribute float roughness; attribute float roughness;
attribute vec3 tangent;
varying vec3 vert; varying vec3 vert;
varying vec3 vertNormal; varying vec3 vertNormal;
varying vec3 vertColor; varying vec3 vertColor;
@ -11,17 +12,50 @@ varying vec2 vertTexCoord;
varying float vertMetalness; varying float vertMetalness;
varying float vertRoughness; varying float vertRoughness;
varying vec3 cameraPos; varying vec3 cameraPos;
varying vec3 firstLightPos;
varying vec3 secondLightPos;
varying vec3 thirdLightPos;
uniform mat4 projectionMatrix; uniform mat4 projectionMatrix;
uniform mat4 modelMatrix; uniform mat4 modelMatrix;
uniform mat3 normalMatrix;
uniform mat4 viewMatrix; 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() void main()
{ {
vert = (modelMatrix * vertex).xyz; vert = (modelMatrix * vertex).xyz;
vertNormal = normalize((modelMatrix * vec4(normal, 1.0)).xyz); vertNormal = normalize((modelMatrix * vec4(normal, 1.0)).xyz);
vertColor = color; 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; vertTexCoord = texCoord;
vertMetalness = metalness; vertMetalness = metalness;
vertRoughness = roughness; vertRoughness = roughness;
cameraPos = vec3(0, 0, -2.1);
gl_Position = projectionMatrix * viewMatrix * vec4(vert, 1.0);
} }

View File

@ -63,9 +63,18 @@ varying highp vec2 vertTexCoord;
varying highp float vertMetalness; varying highp float vertMetalness;
varying highp float vertRoughness; varying highp float vertRoughness;
varying highp vec3 cameraPos; varying highp vec3 cameraPos;
varying vec3 firstLightPos;
varying vec3 secondLightPos;
varying vec3 thirdLightPos;
uniform highp vec3 lightPos; uniform highp vec3 lightPos;
uniform highp sampler2D textureId; uniform highp sampler2D textureId;
uniform highp int textureEnabled; 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 MAX_LIGHTS = 8;
const int TYPE_POINT = 0; const int TYPE_POINT = 0;
@ -275,7 +284,6 @@ void main()
// FIXME: don't hard code here // FIXME: don't hard code here
exposure = 0.0; exposure = 0.0;
gamma = 2.2; gamma = 2.2;
const highp float vertAmbientOcclusion = 1.0;
// Light settings: // Light settings:
// https://doc-snapshots.qt.io/qt5-5.12/qt3d-pbr-materials-lights-qml.html // https://doc-snapshots.qt.io/qt5-5.12/qt3d-pbr-materials-lights-qml.html
@ -283,7 +291,7 @@ void main()
// Key light // Key light
lights[0].type = TYPE_POINT; 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].color = vec3(0.588, 0.588, 0.588);
lights[0].intensity = 5.0; lights[0].intensity = 5.0;
lights[0].constantAttenuation = 0.0; lights[0].constantAttenuation = 0.0;
@ -292,7 +300,7 @@ void main()
// Fill light // Fill light
lights[1].type = TYPE_POINT; 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].color = vec3(0.588, 0.588, 0.588);
lights[1].intensity = 3.0; lights[1].intensity = 3.0;
lights[1].constantAttenuation = 0.0; lights[1].constantAttenuation = 0.0;
@ -301,7 +309,7 @@ void main()
// Rim light // Rim light
lights[2].type = TYPE_POINT; 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].color = vec3(0.588, 0.588, 0.588);
lights[2].intensity = 2.5; lights[2].intensity = 2.5;
lights[2].constantAttenuation = 0.0; lights[2].constantAttenuation = 0.0;
@ -314,13 +322,34 @@ void main()
} }
color = pow(color, vec3(gamma)); 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), gl_FragColor = metalRoughFunction(vec4(color, 1.0),
vertMetalness, metalness,
roughness, roughness,
vertAmbientOcclusion, ambientOcclusion,
vert, vert,
normalize(cameraPos - vert), normalize(cameraPos - vert),
vertNormal); normal);
} }

View File

@ -143,6 +143,7 @@ void AmbientOcclusionBaker::process()
m_resultMesh = new MeshLoader(m_meshResultContext); m_resultMesh = new MeshLoader(m_meshResultContext);
m_resultMesh->setTextureImage(new QImage(*m_textureImage)); m_resultMesh->setTextureImage(new QImage(*m_textureImage));
//m_resultMesh->setNormalMapImage(new QImage(*m_textureImage));
} }
this->moveToThread(QGuiApplication::instance()->thread()); this->moveToThread(QGuiApplication::instance()->thread());

View File

@ -8,6 +8,7 @@
#include "version.h" #include "version.h"
#include "dust3dutil.h" #include "dust3dutil.h"
#include "jointnodetree.h" #include "jointnodetree.h"
#include "meshloader.h"
// Play with glTF online: // Play with glTF online:
// https://gltf-viewer.donmccurdy.com/ // 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["meshes"][0]["primitives"][primitiveIndex]["attributes"]["WEIGHTS_0"] = bufferViewIndex + (++attributeIndex);
} }
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorTexture"]["index"] = 0; m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorTexture"]["index"] = 0;
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = part.second.material.metalness; m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = MeshLoader::m_defaultMetalness;
m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = part.second.material.roughness; m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = MeshLoader::m_defaultRoughness;
primitiveIndex++; primitiveIndex++;

41
src/imageforever.cpp Normal file
View File

@ -0,0 +1,41 @@
#include <map>
#include <QMutex>
#include <QMutexLocker>
#include "imageforever.h"
struct ImageForeverItem
{
QImage *image;
QUuid id;
};
static std::map<QUuid, ImageForeverItem> g_foreverMap;
static std::map<qint64, QUuid> 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;
}

13
src/imageforever.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef IMAGE_FOREVER_H
#define IMAGE_FOREVER_H
#include <QImage>
#include <QUuid>
class ImageForever
{
public:
static const QImage *get(const QUuid &id);
static QUuid add(const QImage *image, QUuid toId=QUuid());
};
#endif

307
src/materialeditwidget.cpp Normal file
View File

@ -0,0 +1,307 @@
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QFormLayout>
#include <QGridLayout>
#include <QMenu>
#include <QWidgetAction>
#include <QLineEdit>
#include <QMessageBox>
#include <QFileDialog>
#include <QLabel>
#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<SkeletonMaterialLayer> 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();
}

58
src/materialeditwidget.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef MATERIAL_EDIT_WIDGET_H
#define MATERIAL_EDIT_WIDGET_H
#include <QDialog>
#include <map>
#include <QCloseEvent>
#include <QLineEdit>
#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<SkeletonMaterialLayer> layers);
void removeMaterial(QUuid materialId);
void setMaterialLayers(QUuid materialId, std::vector<SkeletonMaterialLayer> 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<SkeletonMaterialLayer> 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<SkeletonMaterialLayer> m_layers;
QPushButton *m_textureMapButtons[(int)TextureType::Count - 1] = {nullptr};
};
#endif

343
src/materiallistwidget.cpp Normal file
View File

@ -0,0 +1,343 @@
#include <QGuiApplication>
#include <QMenu>
#include <QXmlStreamWriter>
#include <QClipboard>
#include <QApplication>
#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<QUuid> 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<QUuid> unorderedMaterialIds = m_selectedMaterialIds;
if (!m_currentSelectedMaterialId.isNull())
unorderedMaterialIds.insert(m_currentSelectedMaterialId);
std::vector<QUuid> 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(&copyAction, &QAction::triggered, this, &MaterialListWidget::copy);
contextMenu.addAction(&copyAction);
}
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<QUuid> limitMaterialIds = m_selectedMaterialIds;
if (!m_currentSelectedMaterialId.isNull())
limitMaterialIds.insert(m_currentSelectedMaterialId);
std::set<QUuid> 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);
}

52
src/materiallistwidget.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef MATERIAL_LIST_WIDGET_H
#define MATERIAL_LIST_WIDGET_H
#include <QTreeWidget>
#include <map>
#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<QUuid, std::pair<QTreeWidgetItem *, int>> m_itemMap;
std::set<QUuid> m_selectedMaterialIds;
QUuid m_currentSelectedMaterialId;
QUuid m_shiftStartMaterialId;
bool m_cornerButtonVisible = false;
bool m_hasContextMenu = true;
bool m_multipleSelectionEnabled = true;
};
#endif

View File

@ -0,0 +1,63 @@
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#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);
}

View File

@ -0,0 +1,26 @@
#ifndef MATERIAL_MANAGE_WIDGET_H
#define MATERIAL_MANAGE_WIDGET_H
#include <QWidget>
#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

View File

@ -0,0 +1,122 @@
#include <QGuiApplication>
#include <QElapsedTimer>
#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<SkeletonMaterialLayer> &layers)
{
m_materials.push_back({materialId, layers});
}
const std::set<QUuid> &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<QUuid> 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();
}

View File

@ -0,0 +1,30 @@
#ifndef MATERIAL_PREVIEWS_GENERATOR_H
#define MATERIAL_PREVIEWS_GENERATOR_H
#include <QObject>
#include <map>
#include <QUuid>
#include <vector>
#include "meshloader.h"
#include "skeletondocument.h"
class MaterialPreviewsGenerator : public QObject
{
Q_OBJECT
public:
MaterialPreviewsGenerator();
~MaterialPreviewsGenerator();
void addMaterial(QUuid materialId, const std::vector<SkeletonMaterialLayer> &layers);
const std::set<QUuid> &generatedPreviewMaterialIds();
MeshLoader *takePreview(QUuid materialId);
void generate();
signals:
void finished();
public slots:
void process();
private:
std::vector<std::pair<QUuid, std::vector<SkeletonMaterialLayer>>> m_materials;
std::map<QUuid, MeshLoader *> m_previews;
std::set<QUuid> m_generatedMaterialIds;
};
#endif

107
src/materialwidget.cpp Normal file
View File

@ -0,0 +1,107 @@
#include <QVBoxLayout>
#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);
}

36
src/materialwidget.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef MATERIAL_WIDGET_H
#define MATERIAL_WIDGET_H
#include <QFrame>
#include <QLabel>
#include <QIcon>
#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

View File

@ -11,6 +11,7 @@
#include "positionmap.h" #include "positionmap.h"
#include "meshquadify.h" #include "meshquadify.h"
#include "meshweldseam.h" #include "meshweldseam.h"
#include "imageforever.h"
bool MeshGenerator::m_enableDebug = false; bool MeshGenerator::m_enableDebug = false;
PositionMap<int> *MeshGenerator::m_forMakePositionKey = new PositionMap<int>; PositionMap<int> *MeshGenerator::m_forMakePositionKey = new PositionMap<int>;
@ -30,10 +31,9 @@ void GeneratedCacheContext::updateComponentCombinableMesh(QString componentId, v
cache = cloneCombinableMesh(mesh); cache = cloneCombinableMesh(mesh);
} }
MeshGenerator::MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread) : MeshGenerator::MeshGenerator(SkeletonSnapshot *snapshot) :
m_snapshot(snapshot), m_snapshot(snapshot),
m_mesh(nullptr), m_mesh(nullptr),
m_thread(thread),
m_meshResultContext(nullptr), m_meshResultContext(nullptr),
m_sharedContextWidget(nullptr), m_sharedContextWidget(nullptr),
m_cacheContext(nullptr), m_cacheContext(nullptr),
@ -251,15 +251,33 @@ void *MeshGenerator::combinePartMesh(QString partId)
if (MeshGenerator::m_enableDebug) if (MeshGenerator::m_enableDebug)
meshlite_bmesh_enable_debug(m_meshliteContext, bmeshId, 1); meshlite_bmesh_enable_debug(m_meshliteContext, bmeshId, 1);
float metalness = 0.0; QUuid materialId;
QString metalnessString = valueOfKeyInMapOrEmpty(part, "metalness"); QString materialIdString = valueOfKeyInMapOrEmpty(part, "materialId");
if (!metalnessString.isEmpty()) if (!materialIdString.isEmpty())
metalness = metalnessString.toFloat(); materialId = QUuid(materialIdString);
float roughness = 1.0; Material partMaterial;
QString roughnessString = valueOfKeyInMapOrEmpty(part, "roughness"); for (const auto &material: m_snapshot->materials) {
if (!roughnessString.isEmpty()) if (materialIdString != valueOfKeyInMapOrEmpty(material.first, "id"))
roughness = roughnessString.toFloat(); 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; QString mirroredPartId;
QUuid mirroredPartIdNotAsString; QUuid mirroredPartIdNotAsString;
@ -301,9 +319,8 @@ void *MeshGenerator::combinePartMesh(QString partId)
bmeshNode.origin = QVector3D(x, y, z); bmeshNode.origin = QVector3D(x, y, z);
bmeshNode.radius = radius; bmeshNode.radius = radius;
bmeshNode.nodeId = QUuid(nodeId); bmeshNode.nodeId = QUuid(nodeId);
bmeshNode.material = partMaterial;
bmeshNode.material.color = partColor; bmeshNode.material.color = partColor;
bmeshNode.material.metalness = metalness;
bmeshNode.material.roughness = roughness;
bmeshNode.boneMark = boneMark; bmeshNode.boneMark = boneMark;
//if (SkeletonBoneMark::None != boneMark) //if (SkeletonBoneMark::None != boneMark)
// bmeshNode.color = SkeletonBoneMarkToColor(boneMark); // bmeshNode.color = SkeletonBoneMarkToColor(boneMark);
@ -374,7 +391,7 @@ void *MeshGenerator::combinePartMesh(QString partId)
if (m_requirePreviewPartIds.find(partIdNotAsString) != m_requirePreviewPartIds.end()) { if (m_requirePreviewPartIds.find(partIdNotAsString) != m_requirePreviewPartIds.end()) {
int trimedMeshId = meshlite_trim(m_meshliteContext, meshId, 1); 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); m_generatedPreviewPartIds.insert(partIdNotAsString);
} }
@ -581,7 +598,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
return resultMesh; return resultMesh;
} }
void MeshGenerator::process() void MeshGenerator::generate()
{ {
if (nullptr == m_snapshot) if (nullptr == m_snapshot)
return; return;
@ -714,7 +731,7 @@ void MeshGenerator::process()
if (resultMeshId > 0) { if (resultMeshId > 0) {
loadGeneratedPositionsToMeshResultContext(m_meshliteContext, triangulatedFinalMeshId); 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) { if (needDeleteCacheContext) {
@ -725,6 +742,11 @@ void MeshGenerator::process()
meshlite_destroy_context(m_meshliteContext); meshlite_destroy_context(m_meshliteContext);
qDebug() << "The mesh generation took" << countTimeConsumed.elapsed() << "milliseconds"; qDebug() << "The mesh generation took" << countTimeConsumed.elapsed() << "milliseconds";
}
void MeshGenerator::process()
{
generate();
this->moveToThread(QGuiApplication::instance()->thread()); this->moveToThread(QGuiApplication::instance()->thread());
emit finished(); emit finished();

View File

@ -30,7 +30,7 @@ class MeshGenerator : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread); MeshGenerator(SkeletonSnapshot *snapshot);
~MeshGenerator(); ~MeshGenerator();
void setSharedContextWidget(QOpenGLWidget *widget); void setSharedContextWidget(QOpenGLWidget *widget);
void addPartPreviewRequirement(const QUuid &partId); void addPartPreviewRequirement(const QUuid &partId);
@ -42,6 +42,7 @@ public:
const std::set<QUuid> &requirePreviewPartIds(); const std::set<QUuid> &requirePreviewPartIds();
const std::set<QUuid> &generatedPreviewPartIds(); const std::set<QUuid> &generatedPreviewPartIds();
MeshResultContext *takeMeshResultContext(); MeshResultContext *takeMeshResultContext();
void generate();
signals: signals:
void finished(); void finished();
public slots: public slots:

View File

@ -1,12 +1,18 @@
#include <assert.h> #include <assert.h>
#include <QTextStream>
#include <QFile>
#include "meshloader.h" #include "meshloader.h"
#include "meshlite.h" #include "meshlite.h"
#include "theme.h" #include "theme.h"
#include "positionmap.h" #include "positionmap.h"
#include "ds3file.h"
#define MAX_VERTICES_PER_FACE 100 #define MAX_VERTICES_PER_FACE 100
MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Material material, const std::vector<Material> *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<Material> *triangleMaterials, bool smoothNormal) :
m_triangleVertices(nullptr), m_triangleVertices(nullptr),
m_triangleVertexCount(0), m_triangleVertexCount(0),
m_edgeVertices(nullptr), m_edgeVertices(nullptr),
@ -97,9 +103,9 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Mater
GLfloat *triangleNormals = new GLfloat[triangleCount * 3]; GLfloat *triangleNormals = new GLfloat[triangleCount * 3];
int loadedTriangleNormalItemCount = meshlite_get_triangle_normal_array(meshlite, triangleMesh, triangleNormals, triangleCount * 3); int loadedTriangleNormalItemCount = meshlite_get_triangle_normal_array(meshlite, triangleMesh, triangleNormals, triangleCount * 3);
float modelR = material.color.redF(); float modelR = defaultColor.redF();
float modelG = material.color.greenF(); float modelG = defaultColor.greenF();
float modelB = material.color.blueF(); float modelB = defaultColor.blueF();
m_triangleVertexCount = triangleCount * 3; m_triangleVertexCount = triangleCount * 3;
m_triangleVertices = new Vertex[m_triangleVertexCount * 3]; m_triangleVertices = new Vertex[m_triangleVertexCount * 3];
for (int i = 0; i < triangleCount; i++) { for (int i = 0; i < triangleCount; i++) {
@ -107,15 +113,13 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Mater
float useColorR = modelR; float useColorR = modelR;
float useColorG = modelG; float useColorG = modelG;
float useColorB = modelB; float useColorB = modelB;
float useMetalness = material.metalness; float useMetalness = m_defaultMetalness;
float useRoughness = material.roughness; float useRoughness = m_defaultRoughness;
if (triangleMaterials && i < (int)triangleMaterials->size()) { if (triangleMaterials && i < (int)triangleMaterials->size()) {
auto triangleMaterial = (*triangleMaterials)[i]; auto triangleMaterial = (*triangleMaterials)[i];
useColorR = triangleMaterial.color.redF(); useColorR = triangleMaterial.color.redF();
useColorG = triangleMaterial.color.greenF(); useColorG = triangleMaterial.color.greenF();
useColorB = triangleMaterial.color.blueF(); useColorB = triangleMaterial.color.blueF();
useMetalness = triangleMaterial.metalness;
useRoughness = triangleMaterial.roughness;
} }
TriangulatedFace triangulatedFace; TriangulatedFace triangulatedFace;
triangulatedFace.color.setRedF(useColorR); triangulatedFace.color.setRedF(useColorR);
@ -200,6 +204,9 @@ MeshLoader::MeshLoader(const MeshLoader &mesh) :
if (nullptr != mesh.m_textureImage) { if (nullptr != mesh.m_textureImage) {
this->m_textureImage = new QImage(*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_vertices = mesh.m_vertices;
this->m_faces = mesh.m_faces; this->m_faces = mesh.m_faces;
this->m_triangulatedVertices = mesh.m_triangulatedVertices; this->m_triangulatedVertices = mesh.m_triangulatedVertices;
@ -228,13 +235,15 @@ MeshLoader::MeshLoader(MeshResultContext &resultContext) :
m_triangleVertices = new Vertex[m_triangleVertexCount]; m_triangleVertices = new Vertex[m_triangleVertexCount];
int destIndex = 0; int destIndex = 0;
for (const auto &part: resultContext.parts()) { 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++) { for (auto i = 0; i < 3; i++) {
int vertexIndex = it.indicies[i]; int vertexIndex = it.indicies[i];
const ResultVertex *srcVert = &part.second.vertices[vertexIndex]; const ResultVertex *srcVert = &part.second.vertices[vertexIndex];
const QVector3D *srcNormal = &part.second.interpolatedVertexNormals[vertexIndex]; const QVector3D *srcNormal = &part.second.interpolatedVertexNormals[vertexIndex];
const ResultVertexUv *srcUv = &part.second.vertexUvs[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]; Vertex *dest = &m_triangleVertices[destIndex];
dest->colorR = 0; dest->colorR = 0;
dest->colorG = 0; dest->colorG = 0;
@ -247,8 +256,11 @@ MeshLoader::MeshLoader(MeshResultContext &resultContext) :
dest->normX = srcNormal->x(); dest->normX = srcNormal->x();
dest->normY = srcNormal->y(); dest->normY = srcNormal->y();
dest->normZ = srcNormal->z(); dest->normZ = srcNormal->z();
dest->metalness = srcMaterial->metalness; dest->metalness = m_defaultMetalness;
dest->roughness = srcMaterial->roughness; dest->roughness = m_defaultRoughness;
dest->tangentX = srcTangent->x();
dest->tangentY = srcTangent->y();
dest->tangentZ = srcTangent->z();
destIndex++; destIndex++;
} }
} }
@ -271,6 +283,7 @@ MeshLoader::~MeshLoader()
delete[] m_edgeVertices; delete[] m_edgeVertices;
m_edgeVertexCount = 0; m_edgeVertexCount = 0;
delete m_textureImage; delete m_textureImage;
delete m_normalMapImage;
} }
const std::vector<QVector3D> &MeshLoader::vertices() const std::vector<QVector3D> &MeshLoader::vertices()
@ -322,3 +335,72 @@ const QImage *MeshLoader::textureImage()
{ {
return m_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<QVector3D>::const_iterator it = vertices().begin() ; it != vertices().end(); ++it) {
stream << "v " << (*it).x() << " " << (*it).y() << " " << (*it).z() << endl;
}
for (std::vector<std::vector<int>>::const_iterator it = faces().begin() ; it != faces().end(); ++it) {
stream << "f";
for (std::vector<int>::const_iterator subIt = (*it).begin() ; subIt != (*it).end(); ++subIt) {
stream << " " << (1 + *subIt);
}
stream << endl;
}
}
}

View File

@ -28,6 +28,9 @@ typedef struct
GLfloat texV; GLfloat texV;
GLfloat metalness; GLfloat metalness;
GLfloat roughness; GLfloat roughness;
GLfloat tangentX;
GLfloat tangentY;
GLfloat tangentZ;
} Vertex; } Vertex;
#pragma pack(pop) #pragma pack(pop)
@ -40,7 +43,7 @@ struct TriangulatedFace
class MeshLoader class MeshLoader
{ {
public: public:
MeshLoader(void *meshlite, int meshId, int triangulatedMeshId=-1, Material material={Theme::white, 0.0, 1.0}, const std::vector<Material> *triangleMaterials=nullptr, bool smoothNormal=true); MeshLoader(void *meshlite, int meshId, int triangulatedMeshId=-1, QColor defaultColor=Theme::white, const std::vector<Material> *triangleMaterials=nullptr, bool smoothNormal=true);
MeshLoader(MeshResultContext &resultContext); MeshLoader(MeshResultContext &resultContext);
MeshLoader(Vertex *triangleVertices, int vertexNum); MeshLoader(Vertex *triangleVertices, int vertexNum);
MeshLoader(const MeshLoader &mesh); MeshLoader(const MeshLoader &mesh);
@ -56,6 +59,19 @@ public:
const std::vector<TriangulatedFace> &triangulatedFaces(); const std::vector<TriangulatedFace> &triangulatedFaces();
void setTextureImage(QImage *textureImage); void setTextureImage(QImage *textureImage);
const 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: private:
Vertex *m_triangleVertices = nullptr; Vertex *m_triangleVertices = nullptr;
int m_triangleVertexCount = 0; int m_triangleVertexCount = 0;
@ -66,6 +82,11 @@ private:
std::vector<QVector3D> m_triangulatedVertices; std::vector<QVector3D> m_triangulatedVertices;
std::vector<TriangulatedFace> m_triangulatedFaces; std::vector<TriangulatedFace> m_triangulatedFaces;
QImage *m_textureImage = nullptr; 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 #endif

View File

@ -3,6 +3,8 @@
#include <QDebug> #include <QDebug>
#include <set> #include <set>
#include <cmath> #include <cmath>
#include <QVector2D>
#include "texturegenerator.h"
#include "theme.h" #include "theme.h"
#include "meshresultcontext.h" #include "meshresultcontext.h"
#include "thekla_atlas.h" #include "thekla_atlas.h"
@ -32,7 +34,8 @@ MeshResultContext::MeshResultContext() :
m_resultPartsResolved(false), m_resultPartsResolved(false),
m_resultTriangleUvsResolved(false), m_resultTriangleUvsResolved(false),
m_resultRearrangedVerticesResolved(false), m_resultRearrangedVerticesResolved(false),
m_vertexNormalsInterpolated(false) m_vertexNormalsInterpolated(false),
m_triangleTangentsResolved(false)
{ {
} }
@ -351,6 +354,7 @@ void MeshResultContext::calculateResultParts(std::map<QUuid, ResultPart> &parts)
} }
resultPart.triangles.push_back(newTriangle); resultPart.triangles.push_back(newTriangle);
resultPart.uvs.push_back(triangleUvs()[x]); resultPart.uvs.push_back(triangleUvs()[x]);
resultPart.triangleTangents.push_back(triangleTangents()[x]);
} }
} }
@ -432,7 +436,9 @@ void MeshResultContext::calculateResultTriangleUvs(std::vector<ResultTriangleUv>
Atlas_Options atlasOptions; Atlas_Options atlasOptions;
atlas_set_default_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_Error error = Atlas_Error_Success;
Atlas_Output_Mesh *outputMesh = atlas_generate(&inputMesh, &atlasOptions, &error); Atlas_Output_Mesh *outputMesh = atlas_generate(&inputMesh, &atlasOptions, &error);
@ -555,3 +561,45 @@ const std::vector<QVector3D> &MeshResultContext::interpolatedVertexNormals()
} }
return m_interpolatedVertexNormals; return m_interpolatedVertexNormals;
} }
const std::vector<QVector3D> &MeshResultContext::triangleTangents()
{
if (!m_triangleTangentsResolved) {
m_triangleTangentsResolved = true;
calculateTriangleTangents(m_triangleTangents);
}
return m_triangleTangents;
}
void MeshResultContext::calculateTriangleTangents(std::vector<QVector3D> &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();
}
}

View File

@ -7,14 +7,14 @@
#include <QColor> #include <QColor>
#include "positionmap.h" #include "positionmap.h"
#include "skeletonbonemark.h" #include "skeletonbonemark.h"
#include "texturetype.h"
#define MAX_WEIGHT_NUM 4 #define MAX_WEIGHT_NUM 4
struct Material struct Material
{ {
QColor color; QColor color;
float metalness; const QImage *textureImages[(int)TextureType::Count - 1] = {nullptr};
float roughness;
}; };
struct BmeshNode struct BmeshNode
@ -65,6 +65,7 @@ struct ResultPart
std::vector<ResultTriangle> triangles; std::vector<ResultTriangle> triangles;
std::vector<ResultTriangleUv> uvs; std::vector<ResultTriangleUv> uvs;
std::vector<ResultVertexUv> vertexUvs; std::vector<ResultVertexUv> vertexUvs;
std::vector<QVector3D> triangleTangents;
}; };
struct ResultRearrangedVertex struct ResultRearrangedVertex
@ -99,6 +100,7 @@ public:
const std::vector<ResultRearrangedTriangle> &rearrangedTriangles(); const std::vector<ResultRearrangedTriangle> &rearrangedTriangles();
const std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap(); const std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap();
const std::vector<QVector3D> &interpolatedVertexNormals(); const std::vector<QVector3D> &interpolatedVertexNormals();
const std::vector<QVector3D> &triangleTangents();
private: private:
bool m_triangleSourceResolved; bool m_triangleSourceResolved;
bool m_triangleMaterialResolved; bool m_triangleMaterialResolved;
@ -108,6 +110,7 @@ private:
bool m_resultTriangleUvsResolved; bool m_resultTriangleUvsResolved;
bool m_resultRearrangedVerticesResolved; bool m_resultRearrangedVerticesResolved;
bool m_vertexNormalsInterpolated; bool m_vertexNormalsInterpolated;
bool m_triangleTangentsResolved;
private: private:
std::vector<std::pair<QUuid, QUuid>> m_triangleSourceNodes; std::vector<std::pair<QUuid, QUuid>> m_triangleSourceNodes;
std::vector<Material> m_triangleMaterials; std::vector<Material> m_triangleMaterials;
@ -121,6 +124,7 @@ private:
std::map<int, std::pair<QUuid, QUuid>> m_vertexSourceMap; std::map<int, std::pair<QUuid, QUuid>> m_vertexSourceMap;
std::map<int, int> m_rearrangedVerticesToOldIndexMap; std::map<int, int> m_rearrangedVerticesToOldIndexMap;
std::vector<QVector3D> m_interpolatedVertexNormals; std::vector<QVector3D> m_interpolatedVertexNormals;
std::vector<QVector3D> m_triangleTangents;
private: private:
void calculateTriangleSourceNodes(std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes, std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap); void calculateTriangleSourceNodes(std::vector<std::pair<QUuid, QUuid>> &triangleSourceNodes, std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap);
void calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap); void calculateRemainingVertexSourceNodesAfterTriangleSourceNodesSolved(std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap);
@ -131,6 +135,7 @@ private:
void calculateResultTriangleUvs(std::vector<ResultTriangleUv> &uvs, std::set<int> &seamVertices); void calculateResultTriangleUvs(std::vector<ResultTriangleUv> &uvs, std::set<int> &seamVertices);
void calculateResultRearrangedVertices(std::vector<ResultRearrangedVertex> &rearrangedVertices, std::vector<ResultRearrangedTriangle> &rearrangedTriangles); void calculateResultRearrangedVertices(std::vector<ResultRearrangedVertex> &rearrangedVertices, std::vector<ResultRearrangedTriangle> &rearrangedTriangles);
void interpolateVertexNormals(std::vector<QVector3D> &resultNormals); void interpolateVertexNormals(std::vector<QVector3D> &resultNormals);
void calculateTriangleTangents(std::vector<QVector3D> &tangents);
}; };
#endif #endif

View File

@ -24,6 +24,7 @@ void MeshResultPostProcessor::process()
if (!m_meshResultContext->bmeshNodes.empty()) { if (!m_meshResultContext->bmeshNodes.empty()) {
m_meshResultContext->rearrangedVertices(); m_meshResultContext->rearrangedVertices();
m_meshResultContext->rearrangedTriangles(); m_meshResultContext->rearrangedTriangles();
(void)m_meshResultContext->triangleTangents();
m_meshResultContext->parts(); m_meshResultContext->parts();
} }

View File

@ -16,7 +16,13 @@ ModelMeshBinder::ModelMeshBinder() :
m_newMeshComing(false), m_newMeshComing(false),
m_showWireframes(false), m_showWireframes(false),
m_hasTexture(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_mesh;
delete m_newMesh; delete m_newMesh;
delete m_texture; delete m_texture;
delete m_normalMap;
delete m_metalnessRoughnessAmbientOcclusionMap;
} }
void ModelMeshBinder::updateMesh(MeshLoader *mesh) 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<QVector3D>::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<std::vector<int>>::const_iterator it = m_mesh->faces().begin() ; it != m_mesh->faces().end(); ++it) {
stream << "f";
for (std::vector<int>::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<QString, QColor> 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<QVector3D>::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<TriangulatedFace>::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() void ModelMeshBinder::initialize()
{ {
m_vaoTriangle.create(); m_vaoTriangle.create();
@ -133,12 +70,28 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
delete m_mesh; delete m_mesh;
m_mesh = newMesh; m_mesh = newMesh;
if (m_mesh) { if (m_mesh) {
m_hasTexture = nullptr != m_mesh->textureImage(); m_hasTexture = nullptr != m_mesh->textureImage();
delete m_texture; delete m_texture;
m_texture = nullptr; m_texture = nullptr;
if (m_hasTexture) { if (m_hasTexture)
m_texture = new QOpenGLTexture(*m_mesh->textureImage()); 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); QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoTriangle);
if (m_vboTriangle.isCreated()) if (m_vboTriangle.isCreated())
@ -154,12 +107,14 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
f->glEnableVertexAttribArray(3); f->glEnableVertexAttribArray(3);
f->glEnableVertexAttribArray(4); f->glEnableVertexAttribArray(4);
f->glEnableVertexAttribArray(5); f->glEnableVertexAttribArray(5);
f->glEnableVertexAttribArray(6);
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(3 * sizeof(GLfloat))); f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(3 * sizeof(GLfloat)));
f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(6 * sizeof(GLfloat))); f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(6 * sizeof(GLfloat)));
f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(9 * sizeof(GLfloat))); f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(9 * sizeof(GLfloat)));
f->glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(11 * sizeof(GLfloat))); f->glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(11 * sizeof(GLfloat)));
f->glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(12 * sizeof(GLfloat))); f->glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(12 * sizeof(GLfloat)));
f->glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(13 * sizeof(GLfloat)));
m_vboTriangle.release(); m_vboTriangle.release();
} }
{ {
@ -177,12 +132,14 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
f->glEnableVertexAttribArray(3); f->glEnableVertexAttribArray(3);
f->glEnableVertexAttribArray(4); f->glEnableVertexAttribArray(4);
f->glEnableVertexAttribArray(5); f->glEnableVertexAttribArray(5);
f->glEnableVertexAttribArray(6);
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(3 * sizeof(GLfloat))); f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(3 * sizeof(GLfloat)));
f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(6 * sizeof(GLfloat))); f->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(6 * sizeof(GLfloat)));
f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(9 * sizeof(GLfloat))); f->glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(9 * sizeof(GLfloat)));
f->glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(11 * sizeof(GLfloat))); f->glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(11 * sizeof(GLfloat)));
f->glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(12 * sizeof(GLfloat))); f->glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(12 * sizeof(GLfloat)));
f->glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(13 * sizeof(GLfloat)));
m_vboEdge.release(); m_vboEdge.release();
} }
} else { } else {
@ -197,6 +154,10 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoEdge); QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoEdge);
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
program->setUniformValue(program->textureEnabledLoc(), 0); 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); f->glDrawArrays(GL_LINES, 0, m_renderEdgeVertexCount);
} }
} }
@ -211,6 +172,22 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
} else { } else {
program->setUniformValue(program->textureEnabledLoc(), 0); 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); f->glDrawArrays(GL_TRIANGLES, 0, m_renderTriangleVertexCount);
} }
} }
@ -223,6 +200,10 @@ void ModelMeshBinder::cleanup()
m_vboEdge.destroy(); m_vboEdge.destroy();
delete m_texture; delete m_texture;
m_texture = nullptr; m_texture = nullptr;
delete m_normalMap;
m_normalMap = nullptr;
delete m_metalnessRoughnessAmbientOcclusionMap;
m_metalnessRoughnessAmbientOcclusionMap = nullptr;
} }
void ModelMeshBinder::showWireframes() void ModelMeshBinder::showWireframes()

View File

@ -14,8 +14,6 @@ public:
ModelMeshBinder(); ModelMeshBinder();
~ModelMeshBinder(); ~ModelMeshBinder();
void updateMesh(MeshLoader *mesh); void updateMesh(MeshLoader *mesh);
void exportMeshAsObj(const QString &filename);
void exportMeshAsObjPlusMaterials(const QString &filename);
void initialize(); void initialize();
void paint(ModelShaderProgram *program); void paint(ModelShaderProgram *program);
void cleanup(); void cleanup();
@ -31,6 +29,12 @@ private:
bool m_showWireframes; bool m_showWireframes;
bool m_hasTexture; bool m_hasTexture;
QOpenGLTexture *m_texture; QOpenGLTexture *m_texture;
bool m_hasNormalMap;
QOpenGLTexture *m_normalMap;
bool m_hasMetalnessMap;
bool m_hasRoughnessMap;
bool m_hasAmbientOcclusionMap;
QOpenGLTexture *m_metalnessRoughnessAmbientOcclusionMap;
private: private:
QOpenGLVertexArrayObject m_vaoTriangle; QOpenGLVertexArrayObject m_vaoTriangle;
QOpenGLBuffer m_vboTriangle; QOpenGLBuffer m_vboTriangle;

View File

@ -36,15 +36,23 @@ ModelShaderProgram::ModelShaderProgram(bool usePBR)
this->bindAttributeLocation("texCoord", 3); this->bindAttributeLocation("texCoord", 3);
this->bindAttributeLocation("metalness", 4); this->bindAttributeLocation("metalness", 4);
this->bindAttributeLocation("roughness", 5); this->bindAttributeLocation("roughness", 5);
this->bindAttributeLocation("tangent", 6);
this->link(); this->link();
this->bind(); this->bind();
m_projectionMatrixLoc = this->uniformLocation("projectionMatrix"); m_projectionMatrixLoc = this->uniformLocation("projectionMatrix");
m_modelMatrixLoc = this->uniformLocation("modelMatrix"); m_modelMatrixLoc = this->uniformLocation("modelMatrix");
m_normalMatrixLoc = this->uniformLocation("normalMatrix");
m_viewMatrixLoc = this->uniformLocation("viewMatrix"); m_viewMatrixLoc = this->uniformLocation("viewMatrix");
m_lightPosLoc = this->uniformLocation("lightPos"); m_lightPosLoc = this->uniformLocation("lightPos");
m_textureIdLoc = this->uniformLocation("textureId"); m_textureIdLoc = this->uniformLocation("textureId");
m_textureEnabledLoc = this->uniformLocation("textureEnabled"); 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() int ModelShaderProgram::projectionMatrixLoc()
@ -57,6 +65,11 @@ int ModelShaderProgram::modelMatrixLoc()
return m_modelMatrixLoc; return m_modelMatrixLoc;
} }
int ModelShaderProgram::normalMatrixLoc()
{
return m_normalMatrixLoc;
}
int ModelShaderProgram::viewMatrixLoc() int ModelShaderProgram::viewMatrixLoc()
{ {
return m_viewMatrixLoc; return m_viewMatrixLoc;
@ -76,3 +89,34 @@ int ModelShaderProgram::textureIdLoc()
{ {
return m_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;
}

View File

@ -9,18 +9,32 @@ public:
ModelShaderProgram(bool usePBR=true); ModelShaderProgram(bool usePBR=true);
int projectionMatrixLoc(); int projectionMatrixLoc();
int modelMatrixLoc(); int modelMatrixLoc();
int normalMatrixLoc();
int viewMatrixLoc(); int viewMatrixLoc();
int lightPosLoc(); int lightPosLoc();
int textureIdLoc(); int textureIdLoc();
int textureEnabledLoc(); int textureEnabledLoc();
int normalMapEnabledLoc();
int normalMapIdLoc();
int metalnessMapEnabledLoc();
int roughnessMapEnabledLoc();
int ambientOcclusionMapEnabledLoc();
int metalnessRoughnessAmbientOcclusionMapIdLoc();
static const QString &loadShaderSource(const QString &name); static const QString &loadShaderSource(const QString &name);
private: private:
int m_projectionMatrixLoc; int m_projectionMatrixLoc;
int m_modelMatrixLoc; int m_modelMatrixLoc;
int m_normalMatrixLoc;
int m_viewMatrixLoc; int m_viewMatrixLoc;
int m_lightPosLoc; int m_lightPosLoc;
int m_textureIdLoc; int m_textureIdLoc;
int m_textureEnabledLoc; int m_textureEnabledLoc;
int m_normalMapEnabledLoc;
int m_normalMapIdLoc;
int m_metalnessMapEnabledLoc;
int m_roughnessMapEnabledLoc;
int m_ambientOcclusionMapEnabledLoc;
int m_metalnessRoughnessAmbientOcclusionMapIdLoc;
}; };
#endif #endif

View File

@ -136,9 +136,11 @@ void ModelWidget::initializeGL()
// Our camera never changes in this example. // Our camera never changes in this example.
m_camera.setToIdentity(); m_camera.setToIdentity();
// FIXME: if change here, please also change the camera pos in PBR shader
m_camera.translate(0, 0, -2.1); m_camera.translate(0, 0, -2.1);
// Light position is fixed. // Light position is fixed.
// FIXME: PBR render no longer use this parameter
m_program->setUniformValue(m_program->lightPosLoc(), QVector3D(0, 0, 70)); m_program->setUniformValue(m_program->lightPosLoc(), QVector3D(0, 0, 70));
m_program->release(); m_program->release();
@ -159,8 +161,11 @@ void ModelWidget::paintGL()
m_program->bind(); m_program->bind();
m_program->setUniformValue(m_program->projectionMatrixLoc(), m_projection); m_program->setUniformValue(m_program->projectionMatrixLoc(), m_projection);
m_program->setUniformValue(m_program->modelMatrixLoc(), m_world); 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->viewMatrixLoc(), m_camera);
m_program->setUniformValue(m_program->textureEnabledLoc(), 0); m_program->setUniformValue(m_program->textureEnabledLoc(), 0);
m_program->setUniformValue(m_program->normalMapEnabledLoc(), 0);
m_meshBinder.paint(m_program); m_meshBinder.paint(m_program);
@ -291,16 +296,6 @@ void ModelWidget::updateMesh(MeshLoader *mesh)
update(); 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) void ModelWidget::enableMove(bool enabled)
{ {
m_moveEnabled = enabled; m_moveEnabled = enabled;

View File

@ -28,8 +28,6 @@ public:
m_transparent = t; m_transparent = t;
} }
void updateMesh(MeshLoader *mesh); void updateMesh(MeshLoader *mesh);
void exportMeshAsObj(const QString &filename);
void exportMeshAsObjPlusMaterials(const QString &filename);
void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions); void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions);
void toggleWireframe(); void toggleWireframe();
void enableMove(bool enabled); void enableMove(bool enabled);

View File

@ -9,6 +9,7 @@
#include "skeletondocument.h" #include "skeletondocument.h"
#include "dust3dutil.h" #include "dust3dutil.h"
#include "skeletonxml.h" #include "skeletonxml.h"
#include "materialpreviewsgenerator.h"
unsigned long SkeletonDocument::m_maxSnapshot = 1000; unsigned long SkeletonDocument::m_maxSnapshot = 1000;
@ -54,7 +55,8 @@ SkeletonDocument::SkeletonDocument() :
m_isRigObsolete(false), m_isRigObsolete(false),
m_riggedResultContext(new MeshResultContext), m_riggedResultContext(new MeshResultContext),
m_posePreviewsGenerator(nullptr), 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; 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 const SkeletonMotion *SkeletonDocument::findMotion(QUuid motionId) const
{ {
auto it = motionMap.find(motionId); auto it = motionMap.find(motionId);
@ -830,7 +840,10 @@ void SkeletonDocument::markAllDirty()
} }
void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds, void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds,
SkeletonDocumentToSnapshotFor forWhat, const std::set<QUuid> &limitPoseIds, const std::set<QUuid> &limitMotionIds) const SkeletonDocumentToSnapshotFor forWhat,
const std::set<QUuid> &limitPoseIds,
const std::set<QUuid> &limitMotionIds,
const std::set<QUuid> &limitMaterialIds) const
{ {
if (SkeletonDocumentToSnapshotFor::Document == forWhat || if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
SkeletonDocumentToSnapshotFor::Nodes == forWhat) { SkeletonDocumentToSnapshotFor::Nodes == forWhat) {
@ -874,10 +887,8 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
part["deformWidth"] = QString::number(partIt.second.deformWidth); part["deformWidth"] = QString::number(partIt.second.deformWidth);
if (!partIt.second.name.isEmpty()) if (!partIt.second.name.isEmpty())
part["name"] = partIt.second.name; part["name"] = partIt.second.name;
if (partIt.second.metalnessAdjusted()) if (partIt.second.materialAdjusted())
part["metalness"] = QString::number(partIt.second.metalness); part["materialId"] = partIt.second.materialId.toString();
if (partIt.second.roughnessAdjusted())
part["roughness"] = QString::number(partIt.second.roughness);
snapshot->parts[part["id"]] = part; snapshot->parts[part["id"]] = part;
} }
for (const auto &nodeIt: nodeMap) { for (const auto &nodeIt: nodeMap) {
@ -952,6 +963,38 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
snapshot->rootComponent["children"] = children; snapshot->rootComponent["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<QString, QString> material;
material["id"] = materialIt.second.id.toString();
material["type"] = "MetalRoughness";
if (!materialIt.second.name.isEmpty())
material["name"] = materialIt.second.name;
std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>> layers;
for (const auto &layer: materialIt.second.layers) {
std::vector<std::map<QString, QString>> maps;
for (const auto &mapItem: layer.maps) {
std::map<QString, QString> textureMap;
textureMap["for"] = TextureTypeToString(mapItem.forWhat);
textureMap["linkDataType"] = "imageId";
textureMap["linkData"] = mapItem.imageId.toString();
maps.push_back(textureMap);
}
std::map<QString, QString> layerAttributes;
layers.push_back({layerAttributes, maps});
}
snapshot->materials.push_back(std::make_pair(material, layers));
}
}
if (SkeletonDocumentToSnapshotFor::Document == forWhat || if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
SkeletonDocumentToSnapshotFor::Poses == forWhat) { SkeletonDocumentToSnapshotFor::Poses == forWhat) {
for (const auto &poseId: poseIdList) { for (const auto &poseId: poseIdList) {
@ -1048,6 +1091,43 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
std::set<QUuid> inversePartIds; std::set<QUuid> inversePartIds;
std::map<QUuid, QUuid> oldNewIdMap; std::map<QUuid, QUuid> 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) { for (const auto &partKv: snapshot.parts) {
const auto newUuid = QUuid::createUuid(); const auto newUuid = QUuid::createUuid();
SkeletonPart &part = partMap[newUuid]; SkeletonPart &part = partMap[newUuid];
@ -1075,12 +1155,9 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
const auto &deformWidthIt = partKv.second.find("deformWidth"); const auto &deformWidthIt = partKv.second.find("deformWidth");
if (deformWidthIt != partKv.second.end()) if (deformWidthIt != partKv.second.end())
part.setDeformWidth(deformWidthIt->second.toFloat()); part.setDeformWidth(deformWidthIt->second.toFloat());
const auto &metalnessIt = partKv.second.find("metalness"); const auto &materialIdIt = partKv.second.find("materialId");
if (metalnessIt != partKv.second.end()) if (materialIdIt != partKv.second.end())
part.metalness = metalnessIt->second.toFloat(); part.materialId = oldNewIdMap[QUuid(materialIdIt->second)];
const auto &roughnessIt = partKv.second.find("roughness");
if (roughnessIt != partKv.second.end())
part.roughness = roughnessIt->second.toFloat();
newAddedPartIds.insert(part.id); newAddedPartIds.insert(part.id);
} }
for (const auto &nodeKv: snapshot.nodes) { for (const auto &nodeKv: snapshot.nodes) {
@ -1263,6 +1340,8 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
emit checkEdge(edgeIt); emit checkEdge(edgeIt);
} }
if (!snapshot.materials.empty())
emit materialListChanged();
if (!snapshot.poses.empty()) if (!snapshot.poses.empty())
emit poseListChanged(); emit poseListChanged();
if (!snapshot.motions.empty()) if (!snapshot.motions.empty())
@ -1279,6 +1358,8 @@ void SkeletonDocument::reset()
edgeMap.clear(); edgeMap.clear();
partMap.clear(); partMap.clear();
componentMap.clear(); componentMap.clear();
materialMap.clear();
materialIdList.clear();
poseMap.clear(); poseMap.clear();
poseIdList.clear(); poseIdList.clear();
motionMap.clear(); motionMap.clear();
@ -1305,8 +1386,9 @@ MeshLoader *SkeletonDocument::takeResultMesh()
MeshLoader *SkeletonDocument::takeResultTextureMesh() MeshLoader *SkeletonDocument::takeResultTextureMesh()
{ {
MeshLoader *resultTextureMesh = m_resultTextureMesh; if (nullptr == m_resultTextureMesh)
m_resultTextureMesh = nullptr; return nullptr;
MeshLoader *resultTextureMesh = new MeshLoader(*m_resultTextureMesh);
return resultTextureMesh; return resultTextureMesh;
} }
@ -1413,20 +1495,21 @@ void SkeletonDocument::generateMesh()
SkeletonSnapshot *snapshot = new SkeletonSnapshot; SkeletonSnapshot *snapshot = new SkeletonSnapshot;
toSnapshot(snapshot); toSnapshot(snapshot);
resetDirtyFlags(); resetDirtyFlags();
m_meshGenerator = new MeshGenerator(snapshot, thread); m_meshGenerator = new MeshGenerator(snapshot);
m_meshGenerator->setSmoothNormal(m_smoothNormal); m_meshGenerator->setSmoothNormal(m_smoothNormal);
m_meshGenerator->setWeldEnabled(weldEnabled); m_meshGenerator->setWeldEnabled(weldEnabled);
m_meshGenerator->setGeneratedCacheContext(&m_generatedCacheContext); m_meshGenerator->setGeneratedCacheContext(&m_generatedCacheContext);
if (nullptr != m_sharedContextWidget) if (nullptr != m_sharedContextWidget)
m_meshGenerator->setSharedContextWidget(m_sharedContextWidget); m_meshGenerator->setSharedContextWidget(m_sharedContextWidget);
m_meshGenerator->moveToThread(thread);
for (auto &part: partMap) { for (auto &part: partMap) {
m_meshGenerator->addPartPreviewRequirement(part.first); m_meshGenerator->addPartPreviewRequirement(part.first);
} }
m_meshGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_meshGenerator, &MeshGenerator::process); connect(thread, &QThread::started, m_meshGenerator, &MeshGenerator::process);
connect(m_meshGenerator, &MeshGenerator::finished, this, &SkeletonDocument::meshReady); connect(m_meshGenerator, &MeshGenerator::finished, this, &SkeletonDocument::meshReady);
connect(m_meshGenerator, &MeshGenerator::finished, thread, &QThread::quit); connect(m_meshGenerator, &MeshGenerator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater);
emit meshGenerating();
thread->start(); thread->start();
} }
@ -1442,12 +1525,31 @@ void SkeletonDocument::generateTexture()
m_isTextureObsolete = false; m_isTextureObsolete = false;
QThread *thread = new QThread; 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); m_textureGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_textureGenerator, &TextureGenerator::process); connect(thread, &QThread::started, m_textureGenerator, &TextureGenerator::process);
connect(m_textureGenerator, &TextureGenerator::finished, this, &SkeletonDocument::textureReady); connect(m_textureGenerator, &TextureGenerator::finished, this, &SkeletonDocument::textureReady);
connect(m_textureGenerator, &TextureGenerator::finished, thread, &QThread::quit); connect(m_textureGenerator, &TextureGenerator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater);
emit textureGenerating();
thread->start(); thread->start();
} }
@ -1501,7 +1603,7 @@ void SkeletonDocument::bakeAmbientOcclusionTexture()
QThread *thread = new QThread; QThread *thread = new QThread;
m_ambientOcclusionBaker = new AmbientOcclusionBaker(); m_ambientOcclusionBaker = new AmbientOcclusionBaker();
m_ambientOcclusionBaker->setInputMesh(*m_postProcessedResultContext); 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) if (textureBorderImage)
m_ambientOcclusionBaker->setBorderImage(*textureBorderImage); m_ambientOcclusionBaker->setBorderImage(*textureBorderImage);
if (textureColorImage) if (textureColorImage)
@ -1578,6 +1680,7 @@ void SkeletonDocument::postProcess()
connect(m_postProcessor, &MeshResultPostProcessor::finished, this, &SkeletonDocument::postProcessedMeshResultReady); connect(m_postProcessor, &MeshResultPostProcessor::finished, this, &SkeletonDocument::postProcessedMeshResultReady);
connect(m_postProcessor, &MeshResultPostProcessor::finished, thread, &QThread::quit); connect(m_postProcessor, &MeshResultPostProcessor::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(thread, &QThread::finished, thread, &QThread::deleteLater);
emit postProcessing();
thread->start(); thread->start();
} }
@ -2155,29 +2258,18 @@ void SkeletonDocument::setPartDeformWidth(QUuid partId, float width)
emit skeletonChanged(); emit skeletonChanged();
} }
void SkeletonDocument::setPartMetalness(QUuid partId, float metalness) void SkeletonDocument::setPartMaterialId(QUuid partId, QUuid materialId)
{ {
auto part = partMap.find(partId); auto part = partMap.find(partId);
if (part == partMap.end()) { if (part == partMap.end()) {
qDebug() << "Part not found:" << partId; qDebug() << "Part not found:" << partId;
return; return;
} }
part->second.metalness = metalness; if (part->second.materialId == materialId)
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;
return; return;
} part->second.materialId = materialId;
part->second.roughness = roughness;
part->second.dirty = true; part->second.dirty = true;
emit partRoughnessChanged(partId); emit partMaterialIdChanged(partId);
emit skeletonChanged(); emit skeletonChanged();
} }
@ -2280,6 +2372,17 @@ bool SkeletonDocument::hasPastableNodesInClipboard() const
return false; return false;
} }
bool SkeletonDocument::hasPastableMaterialsInClipboard() const
{
const QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
if (mimeData->hasText()) {
if (-1 != mimeData->text().indexOf("<material "))
return true;
}
return false;
}
bool SkeletonDocument::hasPastablePosesInClipboard() const bool SkeletonDocument::hasPastablePosesInClipboard() const
{ {
const QClipboard *clipboard = QApplication::clipboard(); const QClipboard *clipboard = QApplication::clipboard();
@ -2741,3 +2844,116 @@ void SkeletonDocument::posePreviewsReady()
generatePosePreviews(); generatePosePreviews();
} }
void SkeletonDocument::addMaterial(QString name, std::vector<SkeletonMaterialLayer> 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<SkeletonMaterialLayer> 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();
}

View File

@ -22,6 +22,9 @@
#include "rigtype.h" #include "rigtype.h"
#include "posepreviewsgenerator.h" #include "posepreviewsgenerator.h"
#include "curveutil.h" #include "curveutil.h"
#include "texturetype.h"
class MaterialPreviewsGenerator;
class SkeletonNode class SkeletonNode
{ {
@ -97,8 +100,7 @@ public:
std::vector<QUuid> nodeIds; std::vector<QUuid> nodeIds;
bool dirty; bool dirty;
bool wrapped; bool wrapped;
float metalness; QUuid materialId;
float roughness;
SkeletonPart(const QUuid &withId=QUuid()) : SkeletonPart(const QUuid &withId=QUuid()) :
visible(true), visible(true),
locked(false), locked(false),
@ -112,9 +114,7 @@ public:
color(Theme::white), color(Theme::white),
hasColor(false), hasColor(false),
dirty(true), dirty(true),
wrapped(false), wrapped(false)
metalness(0.0),
roughness(1.0)
{ {
id = withId.isNull() ? QUuid::createUuid() : withId; id = withId.isNull() ? QUuid::createUuid() : withId;
} }
@ -146,17 +146,9 @@ public:
{ {
return deformThicknessAdjusted() || deformWidthAdjusted(); 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 bool materialAdjusted() const
{ {
return metalnessAdjusted() || roughnessAdjusted(); return !materialId.isNull();
} }
bool isEditVisible() const bool isEditVisible() const
{ {
@ -178,8 +170,7 @@ public:
wrapped = other.wrapped; wrapped = other.wrapped;
componentId = other.componentId; componentId = other.componentId;
dirty = other.dirty; dirty = other.dirty;
metalness = other.metalness; materialId = other.materialId;
roughness = other.roughness;
} }
void updatePreviewMesh(MeshLoader *previewMesh) void updatePreviewMesh(MeshLoader *previewMesh)
{ {
@ -411,10 +402,54 @@ private:
Q_DISABLE_COPY(SkeletonMotion); Q_DISABLE_COPY(SkeletonMotion);
}; };
class SkeletonMaterialMap
{
public:
TextureType forWhat;
QUuid imageId;
};
class SkeletonMaterialLayer
{
public:
std::vector<SkeletonMaterialMap> maps;
};
class SkeletonMaterial
{
public:
SkeletonMaterial()
{
}
~SkeletonMaterial()
{
delete m_previewMesh;
}
QUuid id;
QString name;
bool dirty = true;
std::vector<SkeletonMaterialLayer> 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 enum class SkeletonDocumentToSnapshotFor
{ {
Document = 0, Document = 0,
Nodes, Nodes,
Materials,
Poses, Poses,
Motions Motions
}; };
@ -462,8 +497,7 @@ signals:
void partRoundStateChanged(QUuid partId); void partRoundStateChanged(QUuid partId);
void partColorStateChanged(QUuid partId); void partColorStateChanged(QUuid partId);
void partWrapStateChanged(QUuid partId); void partWrapStateChanged(QUuid partId);
void partMetalnessChanged(QUuid partId); void partMaterialIdChanged(QUuid partId);
void partRoughnessChanged(QUuid partId);
void componentInverseStateChanged(QUuid partId); void componentInverseStateChanged(QUuid partId);
void cleanup(); void cleanup();
void originChanged(); void originChanged();
@ -494,6 +528,15 @@ signals:
void motionNameChanged(QUuid motionId); void motionNameChanged(QUuid motionId);
void motionControlNodesChanged(QUuid motionId); void motionControlNodesChanged(QUuid motionId);
void motionKeyframesChanged(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 public: // need initialize
float originX; float originX;
float originY; float originY;
@ -517,6 +560,8 @@ public:
std::map<QUuid, SkeletonNode> nodeMap; std::map<QUuid, SkeletonNode> nodeMap;
std::map<QUuid, SkeletonEdge> edgeMap; std::map<QUuid, SkeletonEdge> edgeMap;
std::map<QUuid, SkeletonComponent> componentMap; std::map<QUuid, SkeletonComponent> componentMap;
std::map<QUuid, SkeletonMaterial> materialMap;
std::vector<QUuid> materialIdList;
std::map<QUuid, SkeletonPose> poseMap; std::map<QUuid, SkeletonPose> poseMap;
std::vector<QUuid> poseIdList; std::vector<QUuid> poseIdList;
std::map<QUuid, SkeletonMotion> motionMap; std::map<QUuid, SkeletonMotion> motionMap;
@ -527,7 +572,8 @@ public:
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(), void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(),
SkeletonDocumentToSnapshotFor forWhat=SkeletonDocumentToSnapshotFor::Document, SkeletonDocumentToSnapshotFor forWhat=SkeletonDocumentToSnapshotFor::Document,
const std::set<QUuid> &limitPoseIds=std::set<QUuid>(), const std::set<QUuid> &limitPoseIds=std::set<QUuid>(),
const std::set<QUuid> &limitMotionIds=std::set<QUuid>()) const; const std::set<QUuid> &limitMotionIds=std::set<QUuid>(),
const std::set<QUuid> &limitMaterialIds=std::set<QUuid>()) const;
void fromSnapshot(const SkeletonSnapshot &snapshot); void fromSnapshot(const SkeletonSnapshot &snapshot);
void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true); void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true);
const SkeletonNode *findNode(QUuid nodeId) const; const SkeletonNode *findNode(QUuid nodeId) const;
@ -537,6 +583,7 @@ public:
const SkeletonComponent *findComponent(QUuid componentId) const; const SkeletonComponent *findComponent(QUuid componentId) const;
const SkeletonComponent *findComponentParent(QUuid componentId) const; const SkeletonComponent *findComponentParent(QUuid componentId) const;
QUuid findComponentParentId(QUuid componentId) const; QUuid findComponentParentId(QUuid componentId) const;
const SkeletonMaterial *findMaterial(QUuid materialId) const;
const SkeletonPose *findPose(QUuid poseId) const; const SkeletonPose *findPose(QUuid poseId) const;
const SkeletonMotion *findMotion(QUuid motionId) const; const SkeletonMotion *findMotion(QUuid motionId) const;
MeshLoader *takeResultMesh(); MeshLoader *takeResultMesh();
@ -547,6 +594,7 @@ public:
void updateTurnaround(const QImage &image); void updateTurnaround(const QImage &image);
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget); void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
bool hasPastableNodesInClipboard() const; bool hasPastableNodesInClipboard() const;
bool hasPastableMaterialsInClipboard() const;
bool hasPastablePosesInClipboard() const; bool hasPastablePosesInClipboard() const;
bool hasPastableMotionsInClipboard() const; bool hasPastableMotionsInClipboard() const;
bool undoable() const; bool undoable() const;
@ -592,6 +640,8 @@ public slots:
void rigReady(); void rigReady();
void generatePosePreviews(); void generatePosePreviews();
void posePreviewsReady(); void posePreviewsReady();
void generateMaterialPreviews();
void materialPreviewsReady();
void setPartLockState(QUuid partId, bool locked); void setPartLockState(QUuid partId, bool locked);
void setPartVisibleState(QUuid partId, bool visible); void setPartVisibleState(QUuid partId, bool visible);
void setPartSubdivState(QUuid partId, bool subdived); void setPartSubdivState(QUuid partId, bool subdived);
@ -603,8 +653,7 @@ public slots:
void setPartRoundState(QUuid partId, bool rounded); void setPartRoundState(QUuid partId, bool rounded);
void setPartColorState(QUuid partId, bool hasColor, QColor color); void setPartColorState(QUuid partId, bool hasColor, QColor color);
void setPartWrapState(QUuid partId, bool wrapped); void setPartWrapState(QUuid partId, bool wrapped);
void setPartMetalness(QUuid partId, float metalness); void setPartMaterialId(QUuid partId, QUuid materialId);
void setPartRoughness(QUuid partId, float roughness);
void setComponentInverseState(QUuid componentId, bool inverse); void setComponentInverseState(QUuid componentId, bool inverse);
void moveComponentUp(QUuid componentId); void moveComponentUp(QUuid componentId);
void moveComponentDown(QUuid componentId); void moveComponentDown(QUuid componentId);
@ -658,6 +707,10 @@ public slots:
void setMotionControlNodes(QUuid motionId, std::vector<HermiteControlNode> controlNodes); void setMotionControlNodes(QUuid motionId, std::vector<HermiteControlNode> controlNodes);
void setMotionKeyframes(QUuid motionId, std::vector<std::pair<float, QUuid>> keyframes); void setMotionKeyframes(QUuid motionId, std::vector<std::pair<float, QUuid>> keyframes);
void renameMotion(QUuid motionId, QString name); void renameMotion(QUuid motionId, QString name);
void addMaterial(QString name, std::vector<SkeletonMaterialLayer>);
void removeMaterial(QUuid materialId);
void setMaterialLayers(QUuid materialId, std::vector<SkeletonMaterialLayer> layers);
void renameMaterial(QUuid materialId, QString name);
private: private:
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId); void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid()); void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
@ -700,6 +753,7 @@ private: // need initialize
MeshResultContext *m_riggedResultContext; MeshResultContext *m_riggedResultContext;
PosePreviewsGenerator *m_posePreviewsGenerator; PosePreviewsGenerator *m_posePreviewsGenerator;
bool m_currentRigSucceed; bool m_currentRigSucceed;
MaterialPreviewsGenerator *m_materialPreviewsGenerator;
private: private:
static unsigned long m_maxSnapshot; static unsigned long m_maxSnapshot;
std::deque<SkeletonHistoryItem> m_undoItems; std::deque<SkeletonHistoryItem> m_undoItems;

View File

@ -31,6 +31,9 @@
#include "rigwidget.h" #include "rigwidget.h"
#include "markiconcreator.h" #include "markiconcreator.h"
#include "motionmanagewidget.h" #include "motionmanagewidget.h"
#include "materialmanagewidget.h"
#include "imageforever.h"
#include "spinnableawesomebutton.h"
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16; int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16; int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
@ -151,6 +154,22 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
QPushButton *rotateClockwiseButton = new QPushButton(QChar(fa::rotateright)); QPushButton *rotateClockwiseButton = new QPushButton(QChar(fa::rotateright));
Theme::initAwesomeButton(rotateClockwiseButton); 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(addButton);
toolButtonLayout->addWidget(selectButton); toolButtonLayout->addWidget(selectButton);
toolButtonLayout->addWidget(dragButton); toolButtonLayout->addWidget(dragButton);
@ -164,6 +183,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
toolButtonLayout->addSpacing(10); toolButtonLayout->addSpacing(10);
toolButtonLayout->addWidget(rotateCounterclockwiseButton); toolButtonLayout->addWidget(rotateCounterclockwiseButton);
toolButtonLayout->addWidget(rotateClockwiseButton); toolButtonLayout->addWidget(rotateClockwiseButton);
toolButtonLayout->addSpacing(10);
toolButtonLayout->addWidget(regenerateButton);
QLabel *verticalLogoLabel = new QLabel; QLabel *verticalLogoLabel = new QLabel;
QImage verticalLogoImage; QImage verticalLogoImage;
@ -216,6 +238,19 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
partTreeWidget->partPreviewChanged(part.first); 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); QDockWidget *rigDocker = new QDockWidget(tr("Rig"), this);
rigDocker->setAllowedAreas(Qt::RightDockWidgetArea); rigDocker->setAllowedAreas(Qt::RightDockWidgetArea);
m_rigWidget = new RigWidget(m_document, rigDocker); m_rigWidget = new RigWidget(m_document, rigDocker);
@ -247,7 +282,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog); connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
addDockWidget(Qt::RightDockWidgetArea, motionDocker); addDockWidget(Qt::RightDockWidgetArea, motionDocker);
tabifyDockWidget(partTreeDocker, rigDocker); tabifyDockWidget(partTreeDocker, materialDocker);
tabifyDockWidget(materialDocker, rigDocker);
tabifyDockWidget(rigDocker, poseDocker); tabifyDockWidget(rigDocker, poseDocker);
tabifyDockWidget(poseDocker, motionDocker); tabifyDockWidget(poseDocker, motionDocker);
@ -495,17 +531,17 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
}); });
m_viewMenu->addAction(m_resetModelWidgetPosAction); m_viewMenu->addAction(m_resetModelWidgetPosAction);
m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this); //m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this);
connect(m_toggleWireframeAction, &QAction::triggered, [=]() { //connect(m_toggleWireframeAction, &QAction::triggered, [=]() {
m_modelRenderWidget->toggleWireframe(); // m_modelRenderWidget->toggleWireframe();
}); //});
m_viewMenu->addAction(m_toggleWireframeAction); //m_viewMenu->addAction(m_toggleWireframeAction);
m_toggleSmoothNormalAction = new QAction(tr("Toggle Smooth Normal"), this); //m_toggleSmoothNormalAction = new QAction(tr("Toggle Smooth Normal"), this);
connect(m_toggleSmoothNormalAction, &QAction::triggered, [=]() { //connect(m_toggleSmoothNormalAction, &QAction::triggered, [=]() {
m_document->toggleSmoothNormal(); // m_document->toggleSmoothNormal();
}); //});
m_viewMenu->addAction(m_toggleSmoothNormalAction); //m_viewMenu->addAction(m_toggleSmoothNormalAction);
connect(m_viewMenu, &QMenu::aboutToShow, [=]() { connect(m_viewMenu, &QMenu::aboutToShow, [=]() {
m_resetModelWidgetPosAction->setEnabled(!isModelSitInVisibleArea(m_modelRenderWidget)); m_resetModelWidgetPosAction->setEnabled(!isModelSitInVisibleArea(m_modelRenderWidget));
@ -520,6 +556,13 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
}); });
m_windowMenu->addAction(m_showPartsListAction); 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); m_showRigAction = new QAction(tr("Rig"), this);
connect(m_showRigAction, &QAction::triggered, [=]() { connect(m_showRigAction, &QAction::triggered, [=]() {
rigDocker->show(); rigDocker->show();
@ -760,30 +803,30 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(m_document, &SkeletonDocument::partRoundStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoundStateChanged); connect(m_document, &SkeletonDocument::partRoundStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoundStateChanged);
connect(m_document, &SkeletonDocument::partWrapStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partWrapStateChanged); connect(m_document, &SkeletonDocument::partWrapStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partWrapStateChanged);
connect(m_document, &SkeletonDocument::partColorStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partColorStateChanged); connect(m_document, &SkeletonDocument::partColorStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partColorStateChanged);
connect(m_document, &SkeletonDocument::partMetalnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partMetalnessChanged); connect(m_document, &SkeletonDocument::partMaterialIdChanged, partTreeWidget, &SkeletonPartTreeWidget::partMaterialIdChanged);
connect(m_document, &SkeletonDocument::partRoughnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoughnessChanged);
connect(m_document, &SkeletonDocument::partRemoved, partTreeWidget, &SkeletonPartTreeWidget::partRemoved); connect(m_document, &SkeletonDocument::partRemoved, partTreeWidget, &SkeletonPartTreeWidget::partRemoved);
connect(m_document, &SkeletonDocument::cleanup, partTreeWidget, &SkeletonPartTreeWidget::removeAllContent); connect(m_document, &SkeletonDocument::cleanup, partTreeWidget, &SkeletonPartTreeWidget::removeAllContent);
connect(m_document, &SkeletonDocument::partChecked, partTreeWidget, &SkeletonPartTreeWidget::partChecked); connect(m_document, &SkeletonDocument::partChecked, partTreeWidget, &SkeletonPartTreeWidget::partChecked);
connect(m_document, &SkeletonDocument::partUnchecked, partTreeWidget, &SkeletonPartTreeWidget::partUnchecked); connect(m_document, &SkeletonDocument::partUnchecked, partTreeWidget, &SkeletonPartTreeWidget::partUnchecked);
connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh); connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() { //connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) { // if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) {
m_document->postProcess(); // 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::resultMeshChanged, m_document, &SkeletonDocument::generateRig);
connect(m_document, &SkeletonDocument::rigChanged, 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::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, [=]() { connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
m_modelRenderWidget->updateMesh(m_document->takeResultMesh()); m_modelRenderWidget->updateMesh(m_document->takeResultMesh());
}); });
//connect(m_document, &SkeletonDocument::resultSkeletonChanged, [=]() {
// m_skeletonRenderWidget->updateMesh(m_document->takeResultSkeletonMesh());
//});
connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() { connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() {
m_modelRenderWidget->setCursor(graphicsWidget->cursor()); m_modelRenderWidget->setCursor(graphicsWidget->cursor());
@ -822,6 +865,15 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
}); });
connect(m_document, &SkeletonDocument::resultRigChanged, m_document, &SkeletonDocument::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); connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
QTimer *timer = new QTimer(this); QTimer *timer = new QTimer(this);
@ -1038,6 +1090,26 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
ds3Writer.add("canvas.png", "asset", &imageByteArray); 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)) { if (ds3Writer.save(filename)) {
setCurrentFilename(filename); setCurrentFilename(filename);
} }
@ -1063,6 +1135,24 @@ void SkeletonDocumentWindow::open()
QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::setOverrideCursor(Qt::WaitCursor);
Ds3FileReader ds3Reader(filename); 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) { for (int i = 0; i < ds3Reader.items().size(); ++i) {
Ds3ReaderItem item = ds3Reader.items().at(i); Ds3ReaderItem item = ds3Reader.items().at(i);
if (item.type == "model") { if (item.type == "model") {
@ -1104,19 +1194,11 @@ void SkeletonDocumentWindow::exportObjResult()
return; return;
} }
QApplication::setOverrideCursor(Qt::WaitCursor); QApplication::setOverrideCursor(Qt::WaitCursor);
m_modelRenderWidget->exportMeshAsObj(filename); MeshLoader *resultMesh = m_document->takeResultMesh();
QApplication::restoreOverrideCursor(); if (nullptr != resultMesh) {
} resultMesh->exportAsObj(filename);
delete resultMesh;
void SkeletonDocumentWindow::exportObjPlusMaterialsResult()
{
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
tr("Wavefront (*.obj)"));
if (filename.isEmpty()) {
return;
} }
QApplication::setOverrideCursor(Qt::WaitCursor);
m_modelRenderWidget->exportMeshAsObjPlusMaterials(filename);
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
} }

View File

@ -39,7 +39,6 @@ public slots:
void saveTo(const QString &saveAsFilename); void saveTo(const QString &saveAsFilename);
void open(); void open();
void exportObjResult(); void exportObjResult();
void exportObjPlusMaterialsResult();
void exportGltfResult(); void exportGltfResult();
void showExportPreview(); void showExportPreview();
void newWindow(); void newWindow();
@ -135,6 +134,7 @@ private:
QMenu *m_windowMenu; QMenu *m_windowMenu;
QAction *m_showPartsListAction; QAction *m_showPartsListAction;
QAction *m_showDebugDialogAction; QAction *m_showDebugDialogAction;
QAction *m_showMaterialsAction;
QAction *m_showRigAction; QAction *m_showRigAction;
QAction *m_showPosesAction; QAction *m_showPosesAction;
QAction *m_showMotionsAction; QAction *m_showMotionsAction;

View File

@ -932,18 +932,7 @@ void SkeletonPartTreeWidget::partColorStateChanged(QUuid partId)
widget->updateColorButton(); widget->updateColorButton();
} }
void SkeletonPartTreeWidget::partMetalnessChanged(QUuid partId) void SkeletonPartTreeWidget::partMaterialIdChanged(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)
{ {
auto item = m_partItemMap.find(partId); auto item = m_partItemMap.find(partId);
if (item == m_partItemMap.end()) { if (item == m_partItemMap.end()) {

View File

@ -62,8 +62,7 @@ public slots:
void partRoundStateChanged(QUuid partId); void partRoundStateChanged(QUuid partId);
void partWrapStateChanged(QUuid partId); void partWrapStateChanged(QUuid partId);
void partColorStateChanged(QUuid partId); void partColorStateChanged(QUuid partId);
void partMetalnessChanged(QUuid partId); void partMaterialIdChanged(QUuid partId);
void partRoughnessChanged(QUuid partId);
void partChecked(QUuid partId); void partChecked(QUuid partId);
void partUnchecked(QUuid partId); void partUnchecked(QUuid partId);
void groupChanged(QTreeWidgetItem *item, int column); void groupChanged(QTreeWidgetItem *item, int column);

View File

@ -5,9 +5,12 @@
#include <QWidgetAction> #include <QWidgetAction>
#include <QColorDialog> #include <QColorDialog>
#include <QSizePolicy> #include <QSizePolicy>
#include <QFileDialog>
#include "skeletonpartwidget.h" #include "skeletonpartwidget.h"
#include "theme.h" #include "theme.h"
#include "floatnumberwidget.h" #include "floatnumberwidget.h"
#include "materiallistwidget.h"
#include "infolabel.h"
SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid partId) : SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid partId) :
m_document(document), 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::setPartRoundState, m_document, &SkeletonDocument::setPartRoundState);
connect(this, &SkeletonPartWidget::setPartWrapState, m_document, &SkeletonDocument::setPartWrapState); connect(this, &SkeletonPartWidget::setPartWrapState, m_document, &SkeletonDocument::setPartWrapState);
connect(this, &SkeletonPartWidget::setPartColorState, m_document, &SkeletonDocument::setPartColorState); connect(this, &SkeletonPartWidget::setPartColorState, m_document, &SkeletonDocument::setPartColorState);
connect(this, &SkeletonPartWidget::setPartMetalness, m_document, &SkeletonDocument::setPartMetalness); connect(this, &SkeletonPartWidget::setPartMaterialId, m_document, &SkeletonDocument::setPartMaterialId);
connect(this, &SkeletonPartWidget::setPartRoughness, m_document, &SkeletonDocument::setPartRoughness);
connect(this, &SkeletonPartWidget::checkPart, m_document, &SkeletonDocument::checkPart); connect(this, &SkeletonPartWidget::checkPart, m_document, &SkeletonDocument::checkPart);
connect(this, &SkeletonPartWidget::enableBackgroundBlur, m_document, &SkeletonDocument::enableBackgroundBlur); connect(this, &SkeletonPartWidget::enableBackgroundBlur, m_document, &SkeletonDocument::enableBackgroundBlur);
connect(this, &SkeletonPartWidget::disableBackgroundBlur, m_document, &SkeletonDocument::disableBackgroundBlur); 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; QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(colorLayout); 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); popup->setLayout(mainLayout);

View File

@ -21,8 +21,7 @@ signals:
void setPartRoundState(QUuid partId, bool rounded); void setPartRoundState(QUuid partId, bool rounded);
void setPartColorState(QUuid partId, bool hasColor, QColor color); void setPartColorState(QUuid partId, bool hasColor, QColor color);
void setPartWrapState(QUuid partId, bool wrapped); void setPartWrapState(QUuid partId, bool wrapped);
void setPartMetalness(QUuid partId, float metalness); void setPartMaterialId(QUuid partId, QUuid materialId);
void setPartRoughness(QUuid partId, float roughness);
void movePartUp(QUuid partId); void movePartUp(QUuid partId);
void movePartDown(QUuid partId); void movePartDown(QUuid partId);
void movePartToTop(QUuid partId); void movePartToTop(QUuid partId);

View File

@ -17,6 +17,7 @@ public:
std::map<QString, QString> rootComponent; std::map<QString, QString> rootComponent;
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> poses; // std::pair<Pose attributes, Bone attributes> std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> poses; // std::pair<Pose attributes, Bone attributes>
std::vector<std::tuple<std::map<QString, QString>, std::vector<std::map<QString, QString>>, std::vector<std::map<QString, QString>>>> motions; // std::tuple<Motion attributes, controlNodes, keyframes> std::vector<std::tuple<std::map<QString, QString>, std::vector<std::map<QString, QString>>, std::vector<std::map<QString, QString>>>> motions; // std::tuple<Motion attributes, controlNodes, keyframes>
std::vector<std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>>>> materials; // std::pair<Material attributes, layers> layer: std::pair<Layer attributes, maps>
public: public:
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString()); void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString());
}; };

View File

@ -91,6 +91,41 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
writer->writeEndElement(); writer->writeEndElement();
} }
writer->writeStartElement("materials");
std::vector<std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>>>>::iterator materialIterator;
for (materialIterator = snapshot->materials.begin(); materialIterator != snapshot->materials.end(); materialIterator++) {
std::map<QString, QString>::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::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>>::iterator layerIterator;
for (layerIterator = materialIterator->second.begin(); layerIterator != materialIterator->second.end(); layerIterator++) {
std::map<QString, QString>::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<std::map<QString, QString>>::iterator mapIterator;
for (mapIterator = layerIterator->second.begin(); mapIterator != layerIterator->second.end(); mapIterator++) {
std::map<QString, QString>::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"); writer->writeStartElement("poses");
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>::iterator poseIterator; std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>::iterator poseIterator;
for (poseIterator = snapshot->poses.begin(); poseIterator != snapshot->poses.end(); poseIterator++) { for (poseIterator = snapshot->poses.begin(); poseIterator != snapshot->poses.end(); poseIterator++) {
@ -165,6 +200,8 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
{ {
std::stack<QString> componentStack; std::stack<QString> componentStack;
std::vector<QString> elementNameStack; std::vector<QString> elementNameStack;
std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>> currentMaterialLayer;
std::pair<std::map<QString, QString>, std::vector<std::pair<std::map<QString, QString>, std::vector<std::map<QString, QString>>>>> currentMaterial;
std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPose; std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPose;
std::tuple<std::map<QString, QString>, std::vector<std::map<QString, QString>>, std::vector<std::map<QString, QString>>> currentMotion; std::tuple<std::map<QString, QString>, std::vector<std::map<QString, QString>>, std::vector<std::map<QString, QString>>> currentMotion;
while (!reader.atEnd()) { while (!reader.atEnd()) {
@ -243,6 +280,25 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
if (!parentChildrenIds.isEmpty()) if (!parentChildrenIds.isEmpty())
parentChildrenIds += ","; parentChildrenIds += ",";
parentChildrenIds += componentId; 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<QString, QString> 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") { } else if (fullName == "canvas.poses.pose") {
QString poseId = reader.attributes().value("id").toString(); QString poseId = reader.attributes().value("id").toString();
if (poseId.isEmpty()) if (poseId.isEmpty())
@ -285,6 +341,10 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
} else if (reader.isEndElement()) { } else if (reader.isEndElement()) {
if (fullName.startsWith("canvas.components.component")) { if (fullName.startsWith("canvas.components.component")) {
componentStack.pop(); 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") { } else if (fullName == "canvas.poses.pose") {
snapshot->poses.push_back(currentPose); snapshot->poses.push_back(currentPose);
} else if (fullName == "canvas.motions.motion") { } else if (fullName == "canvas.motions.motion") {

View File

@ -62,8 +62,8 @@ MeshLoader *SkinnedMeshCreator::createMeshFromTransform(const std::vector<QMatri
currentVertex.normX = sourceNormal.x(); currentVertex.normX = sourceNormal.x();
currentVertex.normY = sourceNormal.y(); currentVertex.normY = sourceNormal.y();
currentVertex.normZ = sourceNormal.z(); currentVertex.normZ = sourceNormal.z();
currentVertex.metalness = triangleMaterial.metalness; currentVertex.metalness = MeshLoader::m_defaultMetalness;
currentVertex.roughness = triangleMaterial.roughness; currentVertex.roughness = MeshLoader::m_defaultRoughness;
} }
} }

View File

@ -1,17 +1,22 @@
#include <QPainter> #include <QPainter>
#include <QGuiApplication> #include <QGuiApplication>
#include <QRegion>
#include <QPolygon>
#include "texturegenerator.h" #include "texturegenerator.h"
#include "theme.h" #include "theme.h"
int TextureGenerator::m_textureWidth = 512; int TextureGenerator::m_textureSize = 1024;
int TextureGenerator::m_textureHeight = 512;
TextureGenerator::TextureGenerator(const MeshResultContext &meshResultContext, QThread *thread) : TextureGenerator::TextureGenerator(const MeshResultContext &meshResultContext) :
m_resultTextureGuideImage(nullptr), m_resultTextureGuideImage(nullptr),
m_resultTextureImage(nullptr), m_resultTextureImage(nullptr),
m_resultTextureBorderImage(nullptr), m_resultTextureBorderImage(nullptr),
m_resultTextureColorImage(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_resultMesh(nullptr)
{ {
m_resultContext = new MeshResultContext(); m_resultContext = new MeshResultContext();
@ -25,6 +30,11 @@ TextureGenerator::~TextureGenerator()
delete m_resultTextureImage; delete m_resultTextureImage;
delete m_resultTextureBorderImage; delete m_resultTextureBorderImage;
delete m_resultTextureColorImage; delete m_resultTextureColorImage;
delete m_resultTextureNormalImage;
delete m_resultTextureMetalnessRoughnessAmbientOcclusionImage;
delete m_resultTextureRoughnessImage;
delete m_resultTextureMetalnessImage;
delete m_resultTextureAmbientOcclusionImage;
delete m_resultMesh; delete m_resultMesh;
} }
@ -56,6 +66,13 @@ QImage *TextureGenerator::takeResultTextureColorImage()
return resultTextureColorImage; return resultTextureColorImage;
} }
QImage *TextureGenerator::takeResultTextureNormalImage()
{
QImage *resultTextureNormalImage = m_resultTextureNormalImage;
m_resultTextureNormalImage = nullptr;
return resultTextureNormalImage;
}
MeshResultContext *TextureGenerator::takeResultContext() MeshResultContext *TextureGenerator::takeResultContext()
{ {
MeshResultContext *resultContext = m_resultContext; MeshResultContext *resultContext = m_resultContext;
@ -70,17 +87,80 @@ MeshLoader *TextureGenerator::takeResultMesh()
return resultMesh; 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<Material> &triangleMaterials = m_resultContext->triangleMaterials(); const std::vector<Material> &triangleMaterials = m_resultContext->triangleMaterials();
const std::vector<ResultTriangleUv> &triangleUvs = m_resultContext->triangleUvs(); const std::vector<ResultTriangleUv> &triangleUvs = m_resultContext->triangleUvs();
m_resultTextureColorImage = new QImage(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight, QImage::Format_ARGB32); m_resultTextureColorImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureColorImage->fill(Qt::transparent); 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_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; QColor borderColor = Qt::darkGray;
QPen pen(borderColor); QPen pen(borderColor);
@ -94,17 +174,38 @@ void TextureGenerator::process()
textureBorderPainter.setRenderHint(QPainter::Antialiasing); textureBorderPainter.setRenderHint(QPainter::Antialiasing);
textureBorderPainter.setRenderHint(QPainter::HighQualityAntialiasing); 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 // round 1, paint background
for (auto i = 0u; i < triangleUvs.size(); i++) { for (auto i = 0u; i < triangleUvs.size(); i++) {
QPainterPath path; QPainterPath path;
const ResultTriangleUv *uv = &triangleUvs[i]; const ResultTriangleUv *uv = &triangleUvs[i];
for (auto j = 0; j < 3; j++) { for (auto j = 0; j < 3; j++) {
if (0 == 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 { } 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); QPen textureBorderPen(triangleMaterials[i].color);
textureBorderPen.setWidth(32); textureBorderPen.setWidth(32);
texturePainter.setPen(textureBorderPen); texturePainter.setPen(textureBorderPen);
@ -116,14 +217,82 @@ void TextureGenerator::process()
for (auto i = 0u; i < triangleUvs.size(); i++) { for (auto i = 0u; i < triangleUvs.size(); i++) {
QPainterPath path; QPainterPath path;
const ResultTriangleUv *uv = &triangleUvs[i]; const ResultTriangleUv *uv = &triangleUvs[i];
for (auto j = 0; j < 3; j++) { float points[][2] = {
if (0 == j) { {uv->uv[0][0] * TextureGenerator::m_textureSize, uv->uv[0][1] * TextureGenerator::m_textureSize},
path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); {uv->uv[1][0] * TextureGenerator::m_textureSize, uv->uv[1][1] * TextureGenerator::m_textureSize},
} else { {uv->uv[2][0] * TextureGenerator::m_textureSize, uv->uv[2][1] * TextureGenerator::m_textureSize}
path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); };
} 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)); texturePainter.fillPath(path, QBrush(triangleMaterials[i].color));
// Copy color texture if there is one
const std::pair<QUuid, QUuid> 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); pen.setWidth(0);
@ -133,13 +302,40 @@ void TextureGenerator::process()
for (auto j = 0; j < 3; j++) { for (auto j = 0; j < 3; j++) {
int from = j; int from = j;
int to = (j + 1) % 3; int to = (j + 1) % 3;
textureBorderPainter.drawLine(uv->uv[from][0] * TextureGenerator::m_textureWidth, uv->uv[from][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_textureWidth, uv->uv[to][1] * TextureGenerator::m_textureHeight); uv->uv[to][0] * TextureGenerator::m_textureSize, uv->uv[to][1] * TextureGenerator::m_textureSize);
} }
} }
texturePainter.end(); texturePainter.end();
textureBorderPainter.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); m_resultTextureImage = new QImage(*m_resultTextureColorImage);
@ -151,8 +347,20 @@ void TextureGenerator::process()
m_resultMesh = new MeshLoader(*m_resultContext); m_resultMesh = new MeshLoader(*m_resultContext);
m_resultMesh->setTextureImage(new QImage(*m_resultTextureImage)); 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()); this->moveToThread(QGuiApplication::instance()->thread());
emit finished(); emit finished();
} }

View File

@ -10,29 +10,46 @@ class TextureGenerator : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
TextureGenerator(const MeshResultContext &meshResultContext, QThread *thread); TextureGenerator(const MeshResultContext &meshResultContext);
~TextureGenerator(); ~TextureGenerator();
QImage *takeResultTextureGuideImage(); QImage *takeResultTextureGuideImage();
QImage *takeResultTextureImage(); QImage *takeResultTextureImage();
QImage *takeResultTextureBorderImage(); QImage *takeResultTextureBorderImage();
QImage *takeResultTextureColorImage(); QImage *takeResultTextureColorImage();
QImage *takeResultTextureNormalImage();
MeshResultContext *takeResultContext(); MeshResultContext *takeResultContext();
MeshLoader *takeResultMesh(); 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: signals:
void finished(); void finished();
public slots: public slots:
void process(); void process();
public: public:
static int m_textureWidth; static int m_textureSize;
static int m_textureHeight; private:
QPainterPath expandedPainterPath(const QPainterPath &painterPath);
private: private:
MeshResultContext *m_resultContext; MeshResultContext *m_resultContext;
QImage *m_resultTextureGuideImage; QImage *m_resultTextureGuideImage;
QImage *m_resultTextureImage; QImage *m_resultTextureImage;
QImage *m_resultTextureBorderImage; QImage *m_resultTextureBorderImage;
QImage *m_resultTextureColorImage; QImage *m_resultTextureColorImage;
QThread *m_thread; QImage *m_resultTextureNormalImage;
QImage *m_resultTextureMetalnessRoughnessAmbientOcclusionImage;
QImage *m_resultTextureRoughnessImage;
QImage *m_resultTextureMetalnessImage;
QImage *m_resultTextureAmbientOcclusionImage;
MeshLoader *m_resultMesh; MeshLoader *m_resultMesh;
std::map<QUuid, QImage> m_partColorTextureMap;
std::map<QUuid, QImage> m_partNormalTextureMap;
std::map<QUuid, QImage> m_partMetalnessTextureMap;
std::map<QUuid, QImage> m_partRoughnessTextureMap;
std::map<QUuid, QImage> m_partAmbientOcclusionTextureMap;
}; };
#endif #endif

5
src/texturetype.cpp Normal file
View File

@ -0,0 +1,5 @@
#include "texturetype.h"
IMPL_TextureTypeToDispName
IMPL_TextureTypeToString
IMPL_TextureTypeFromString

79
src/texturetype.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef TEXTURE_TYPE_H
#define TEXTURE_TYPE_H
#include <QObject>
#include <QColor>
#include <QString>
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

View File

@ -34,6 +34,7 @@ int Theme::toolIconSize = 24;
int Theme::miniIconFontSize = 9; int Theme::miniIconFontSize = 9;
int Theme::miniIconSize = 15; int Theme::miniIconSize = 15;
int Theme::partPreviewImageSize = (Theme::miniIconSize * 3); int Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
int Theme::materialPreviewImageSize = 75;
int Theme::posePreviewImageSize = 75; int Theme::posePreviewImageSize = 75;
int Theme::motionPreviewImageSize = 75; int Theme::motionPreviewImageSize = 75;
int Theme::sidebarPreferredWidth = 200; int Theme::sidebarPreferredWidth = 200;

View File

@ -34,6 +34,7 @@ public:
static QWidget *createVerticalLineWidget(); static QWidget *createVerticalLineWidget();
static int toolIconFontSize; static int toolIconFontSize;
static int toolIconSize; static int toolIconSize;
static int materialPreviewImageSize;
static int posePreviewImageSize; static int posePreviewImageSize;
static int partPreviewImageSize; static int partPreviewImageSize;
static int motionPreviewImageSize; static int motionPreviewImageSize;