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
HEADERS += src/animationclipplayer.h
SOURCES += src/texturetype.cpp
HEADERS += src/texturetype.h
SOURCES += src/imageforever.cpp
HEADERS += src/imageforever.h
SOURCES += src/materialeditwidget.cpp
HEADERS += src/materialeditwidget.h
SOURCES += src/materiallistwidget.cpp
HEADERS += src/materiallistwidget.h
SOURCES += src/materialmanagewidget.cpp
HEADERS += src/materialmanagewidget.h
SOURCES += src/materialpreviewsgenerator.cpp
HEADERS += src/materialpreviewsgenerator.h
SOURCES += src/materialwidget.cpp
HEADERS += src/materialwidget.h
SOURCES += src/main.cpp
HEADERS += src/version.h

View File

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

Binary file not shown.

View File

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

View File

@ -4,6 +4,7 @@ attribute vec3 color;
attribute vec2 texCoord;
attribute float metalness;
attribute float roughness;
attribute vec3 tangent;
varying vec3 vert;
varying vec3 vertNormal;
varying vec3 vertColor;
@ -11,17 +12,50 @@ varying vec2 vertTexCoord;
varying float vertMetalness;
varying float vertRoughness;
varying vec3 cameraPos;
varying vec3 firstLightPos;
varying vec3 secondLightPos;
varying vec3 thirdLightPos;
uniform mat4 projectionMatrix;
uniform mat4 modelMatrix;
uniform mat3 normalMatrix;
uniform mat4 viewMatrix;
uniform highp int normalMapEnabled;
mat3 transpose(mat3 m)
{
return mat3(m[0][0], m[1][0], m[2][0],
m[0][1], m[1][1], m[2][1],
m[0][2], m[1][2], m[2][2]);
}
void main()
{
vert = (modelMatrix * vertex).xyz;
vertNormal = normalize((modelMatrix * vec4(normal, 1.0)).xyz);
vertColor = color;
cameraPos = vec3(0, 0, -2.1);
firstLightPos = vec3(5.0, 5.0, 5.0);
secondLightPos = vec3(-5.0, 5.0, 5.0);
thirdLightPos = vec3(0.0, -5.0, -5.0);
gl_Position = projectionMatrix * viewMatrix * vec4(vert, 1.0);
if (normalMapEnabled == 1) {
vec3 T = normalize(normalMatrix * tangent);
vec3 N = normalize(normalMatrix * normal);
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);
mat3 TBN = transpose(mat3(T, B, N));
firstLightPos = TBN * firstLightPos;
secondLightPos = TBN * secondLightPos;
thirdLightPos = TBN * thirdLightPos;
cameraPos = TBN * cameraPos;
vert = TBN * vert;
}
vertTexCoord = texCoord;
vertMetalness = metalness;
vertRoughness = roughness;
cameraPos = vec3(0, 0, -2.1);
gl_Position = projectionMatrix * viewMatrix * vec4(vert, 1.0);
}

View File

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

View File

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

View File

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

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

View File

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

View File

@ -1,12 +1,18 @@
#include <assert.h>
#include <QTextStream>
#include <QFile>
#include "meshloader.h"
#include "meshlite.h"
#include "theme.h"
#include "positionmap.h"
#include "ds3file.h"
#define MAX_VERTICES_PER_FACE 100
MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Material material, const std::vector<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_triangleVertexCount(0),
m_edgeVertices(nullptr),
@ -97,9 +103,9 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Mater
GLfloat *triangleNormals = new GLfloat[triangleCount * 3];
int loadedTriangleNormalItemCount = meshlite_get_triangle_normal_array(meshlite, triangleMesh, triangleNormals, triangleCount * 3);
float modelR = material.color.redF();
float modelG = material.color.greenF();
float modelB = material.color.blueF();
float modelR = defaultColor.redF();
float modelG = defaultColor.greenF();
float modelB = defaultColor.blueF();
m_triangleVertexCount = triangleCount * 3;
m_triangleVertices = new Vertex[m_triangleVertexCount * 3];
for (int i = 0; i < triangleCount; i++) {
@ -107,15 +113,13 @@ MeshLoader::MeshLoader(void *meshlite, int meshId, int triangulatedMeshId, Mater
float useColorR = modelR;
float useColorG = modelG;
float useColorB = modelB;
float useMetalness = material.metalness;
float useRoughness = material.roughness;
float useMetalness = m_defaultMetalness;
float useRoughness = m_defaultRoughness;
if (triangleMaterials && i < (int)triangleMaterials->size()) {
auto triangleMaterial = (*triangleMaterials)[i];
useColorR = triangleMaterial.color.redF();
useColorG = triangleMaterial.color.greenF();
useColorB = triangleMaterial.color.blueF();
useMetalness = triangleMaterial.metalness;
useRoughness = triangleMaterial.roughness;
}
TriangulatedFace triangulatedFace;
triangulatedFace.color.setRedF(useColorR);
@ -200,6 +204,9 @@ MeshLoader::MeshLoader(const MeshLoader &mesh) :
if (nullptr != mesh.m_textureImage) {
this->m_textureImage = new QImage(*mesh.m_textureImage);
}
if (nullptr != mesh.m_normalMapImage) {
this->m_normalMapImage = new QImage(*mesh.m_normalMapImage);
}
this->m_vertices = mesh.m_vertices;
this->m_faces = mesh.m_faces;
this->m_triangulatedVertices = mesh.m_triangulatedVertices;
@ -228,13 +235,15 @@ MeshLoader::MeshLoader(MeshResultContext &resultContext) :
m_triangleVertices = new Vertex[m_triangleVertexCount];
int destIndex = 0;
for (const auto &part: resultContext.parts()) {
for (const auto &it: part.second.triangles) {
for (int x = 0; x < (int)part.second.triangles.size(); x++) {
const auto &it = part.second.triangles[x];
for (auto i = 0; i < 3; i++) {
int vertexIndex = it.indicies[i];
const ResultVertex *srcVert = &part.second.vertices[vertexIndex];
const QVector3D *srcNormal = &part.second.interpolatedVertexNormals[vertexIndex];
const ResultVertexUv *srcUv = &part.second.vertexUvs[vertexIndex];
const Material *srcMaterial = &part.second.material;
//const Material *srcMaterial = &part.second.material;
const QVector3D *srcTangent = &part.second.triangleTangents[x];
Vertex *dest = &m_triangleVertices[destIndex];
dest->colorR = 0;
dest->colorG = 0;
@ -247,8 +256,11 @@ MeshLoader::MeshLoader(MeshResultContext &resultContext) :
dest->normX = srcNormal->x();
dest->normY = srcNormal->y();
dest->normZ = srcNormal->z();
dest->metalness = srcMaterial->metalness;
dest->roughness = srcMaterial->roughness;
dest->metalness = m_defaultMetalness;
dest->roughness = m_defaultRoughness;
dest->tangentX = srcTangent->x();
dest->tangentY = srcTangent->y();
dest->tangentZ = srcTangent->z();
destIndex++;
}
}
@ -271,6 +283,7 @@ MeshLoader::~MeshLoader()
delete[] m_edgeVertices;
m_edgeVertexCount = 0;
delete m_textureImage;
delete m_normalMapImage;
}
const std::vector<QVector3D> &MeshLoader::vertices()
@ -322,3 +335,72 @@ const QImage *MeshLoader::textureImage()
{
return m_textureImage;
}
void MeshLoader::setNormalMapImage(QImage *normalMapImage)
{
m_normalMapImage = normalMapImage;
}
const QImage *MeshLoader::normalMapImage()
{
return m_normalMapImage;
}
const QImage *MeshLoader::metalnessRoughnessAmbientOcclusionImage()
{
return m_metalnessRoughnessAmbientOcclusionImage;
}
void MeshLoader::setMetalnessRoughnessAmbientOcclusionImage(QImage *image)
{
m_metalnessRoughnessAmbientOcclusionImage = image;
}
bool MeshLoader::hasMetalnessInImage()
{
return m_hasMetalnessInImage;
}
void MeshLoader::setHasMetalnessInImage(bool hasInImage)
{
m_hasMetalnessInImage = hasInImage;
}
bool MeshLoader::hasRoughnessInImage()
{
return m_hasRoughnessInImage;
}
void MeshLoader::setHasRoughnessInImage(bool hasInImage)
{
m_hasRoughnessInImage = hasInImage;
}
bool MeshLoader::hasAmbientOcclusionInImage()
{
return m_hasAmbientOcclusionInImage;
}
void MeshLoader::setHasAmbientOcclusionInImage(bool hasInImage)
{
m_hasAmbientOcclusionInImage = hasInImage;
}
void MeshLoader::exportAsObj(const QString &filename)
{
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
stream << "# " << Ds3FileReader::m_applicationName << endl;
for (std::vector<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 metalness;
GLfloat roughness;
GLfloat tangentX;
GLfloat tangentY;
GLfloat tangentZ;
} Vertex;
#pragma pack(pop)
@ -40,7 +43,7 @@ struct TriangulatedFace
class MeshLoader
{
public:
MeshLoader(void *meshlite, int meshId, int triangulatedMeshId=-1, Material material={Theme::white, 0.0, 1.0}, const std::vector<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(Vertex *triangleVertices, int vertexNum);
MeshLoader(const MeshLoader &mesh);
@ -56,6 +59,19 @@ public:
const std::vector<TriangulatedFace> &triangulatedFaces();
void setTextureImage(QImage *textureImage);
const QImage *textureImage();
void setNormalMapImage(QImage *normalMapImage);
const QImage *normalMapImage();
const QImage *metalnessRoughnessAmbientOcclusionImage();
void setMetalnessRoughnessAmbientOcclusionImage(QImage *image);
bool hasMetalnessInImage();
void setHasMetalnessInImage(bool hasInImage);
bool hasRoughnessInImage();
void setHasRoughnessInImage(bool hasInImage);
bool hasAmbientOcclusionInImage();
void setHasAmbientOcclusionInImage(bool hasInImage);
static float m_defaultMetalness;
static float m_defaultRoughness;
void exportAsObj(const QString &filename);
private:
Vertex *m_triangleVertices = nullptr;
int m_triangleVertexCount = 0;
@ -66,6 +82,11 @@ private:
std::vector<QVector3D> m_triangulatedVertices;
std::vector<TriangulatedFace> m_triangulatedFaces;
QImage *m_textureImage = nullptr;
QImage *m_normalMapImage = nullptr;
QImage *m_metalnessRoughnessAmbientOcclusionImage = nullptr;
bool m_hasMetalnessInImage = false;
bool m_hasRoughnessInImage = false;
bool m_hasAmbientOcclusionInImage = false;
};
#endif

View File

@ -3,6 +3,8 @@
#include <QDebug>
#include <set>
#include <cmath>
#include <QVector2D>
#include "texturegenerator.h"
#include "theme.h"
#include "meshresultcontext.h"
#include "thekla_atlas.h"
@ -32,7 +34,8 @@ MeshResultContext::MeshResultContext() :
m_resultPartsResolved(false),
m_resultTriangleUvsResolved(false),
m_resultRearrangedVerticesResolved(false),
m_vertexNormalsInterpolated(false)
m_vertexNormalsInterpolated(false),
m_triangleTangentsResolved(false)
{
}
@ -351,6 +354,7 @@ void MeshResultContext::calculateResultParts(std::map<QUuid, ResultPart> &parts)
}
resultPart.triangles.push_back(newTriangle);
resultPart.uvs.push_back(triangleUvs()[x]);
resultPart.triangleTangents.push_back(triangleTangents()[x]);
}
}
@ -432,7 +436,9 @@ void MeshResultContext::calculateResultTriangleUvs(std::vector<ResultTriangleUv>
Atlas_Options atlasOptions;
atlas_set_default_options(&atlasOptions);
atlasOptions.packer_options.witness.packing_quality = 4;
atlasOptions.packer_options.witness.packing_quality = 1;
//atlasOptions.packer_options.witness.texel_area = 1.0 / TextureGenerator::m_textureSize;
atlasOptions.packer_options.witness.conservative = false;
Atlas_Error error = Atlas_Error_Success;
Atlas_Output_Mesh *outputMesh = atlas_generate(&inputMesh, &atlasOptions, &error);
@ -555,3 +561,45 @@ const std::vector<QVector3D> &MeshResultContext::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 "positionmap.h"
#include "skeletonbonemark.h"
#include "texturetype.h"
#define MAX_WEIGHT_NUM 4
struct Material
{
QColor color;
float metalness;
float roughness;
const QImage *textureImages[(int)TextureType::Count - 1] = {nullptr};
};
struct BmeshNode
@ -65,6 +65,7 @@ struct ResultPart
std::vector<ResultTriangle> triangles;
std::vector<ResultTriangleUv> uvs;
std::vector<ResultVertexUv> vertexUvs;
std::vector<QVector3D> triangleTangents;
};
struct ResultRearrangedVertex
@ -99,6 +100,7 @@ public:
const std::vector<ResultRearrangedTriangle> &rearrangedTriangles();
const std::map<int, std::pair<QUuid, QUuid>> &vertexSourceMap();
const std::vector<QVector3D> &interpolatedVertexNormals();
const std::vector<QVector3D> &triangleTangents();
private:
bool m_triangleSourceResolved;
bool m_triangleMaterialResolved;
@ -108,6 +110,7 @@ private:
bool m_resultTriangleUvsResolved;
bool m_resultRearrangedVerticesResolved;
bool m_vertexNormalsInterpolated;
bool m_triangleTangentsResolved;
private:
std::vector<std::pair<QUuid, QUuid>> m_triangleSourceNodes;
std::vector<Material> m_triangleMaterials;
@ -121,6 +124,7 @@ private:
std::map<int, std::pair<QUuid, QUuid>> m_vertexSourceMap;
std::map<int, int> m_rearrangedVerticesToOldIndexMap;
std::vector<QVector3D> m_interpolatedVertexNormals;
std::vector<QVector3D> m_triangleTangents;
private:
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);
@ -131,6 +135,7 @@ private:
void calculateResultTriangleUvs(std::vector<ResultTriangleUv> &uvs, std::set<int> &seamVertices);
void calculateResultRearrangedVertices(std::vector<ResultRearrangedVertex> &rearrangedVertices, std::vector<ResultRearrangedTriangle> &rearrangedTriangles);
void interpolateVertexNormals(std::vector<QVector3D> &resultNormals);
void calculateTriangleTangents(std::vector<QVector3D> &tangents);
};
#endif

View File

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

View File

@ -16,7 +16,13 @@ ModelMeshBinder::ModelMeshBinder() :
m_newMeshComing(false),
m_showWireframes(false),
m_hasTexture(false),
m_texture(nullptr)
m_texture(nullptr),
m_hasNormalMap(false),
m_normalMap(nullptr),
m_hasMetalnessMap(false),
m_hasRoughnessMap(false),
m_hasAmbientOcclusionMap(false),
m_metalnessRoughnessAmbientOcclusionMap(nullptr)
{
}
@ -25,6 +31,8 @@ ModelMeshBinder::~ModelMeshBinder()
delete m_mesh;
delete m_newMesh;
delete m_texture;
delete m_normalMap;
delete m_metalnessRoughnessAmbientOcclusionMap;
}
void ModelMeshBinder::updateMesh(MeshLoader *mesh)
@ -37,77 +45,6 @@ void ModelMeshBinder::updateMesh(MeshLoader *mesh)
}
}
void ModelMeshBinder::exportMeshAsObj(const QString &filename)
{
QMutexLocker lock(&m_meshMutex);
if (m_mesh) {
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
QTextStream stream(&file);
stream << "# " << Ds3FileReader::m_applicationName << endl;
for (std::vector<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()
{
m_vaoTriangle.create();
@ -133,12 +70,28 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
delete m_mesh;
m_mesh = newMesh;
if (m_mesh) {
m_hasTexture = nullptr != m_mesh->textureImage();
delete m_texture;
m_texture = nullptr;
if (m_hasTexture) {
if (m_hasTexture)
m_texture = new QOpenGLTexture(*m_mesh->textureImage());
}
m_hasNormalMap = nullptr != m_mesh->normalMapImage();
delete m_normalMap;
m_normalMap = nullptr;
if (m_hasNormalMap)
m_normalMap = new QOpenGLTexture(*m_mesh->normalMapImage());
m_hasMetalnessMap = m_mesh->hasMetalnessInImage();
m_hasRoughnessMap = m_mesh->hasRoughnessInImage();
m_hasAmbientOcclusionMap = m_mesh->hasAmbientOcclusionInImage();
delete m_metalnessRoughnessAmbientOcclusionMap;
m_metalnessRoughnessAmbientOcclusionMap = nullptr;
if (nullptr != m_mesh->metalnessRoughnessAmbientOcclusionImage() &&
(m_hasMetalnessMap || m_hasRoughnessMap || m_hasAmbientOcclusionMap))
m_metalnessRoughnessAmbientOcclusionMap = new QOpenGLTexture(*m_mesh->metalnessRoughnessAmbientOcclusionImage());
{
QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoTriangle);
if (m_vboTriangle.isCreated())
@ -154,12 +107,14 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
f->glEnableVertexAttribArray(3);
f->glEnableVertexAttribArray(4);
f->glEnableVertexAttribArray(5);
f->glEnableVertexAttribArray(6);
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(3 * 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(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(6, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(13 * sizeof(GLfloat)));
m_vboTriangle.release();
}
{
@ -177,12 +132,14 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
f->glEnableVertexAttribArray(3);
f->glEnableVertexAttribArray(4);
f->glEnableVertexAttribArray(5);
f->glEnableVertexAttribArray(6);
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(3 * 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(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(6, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<void *>(13 * sizeof(GLfloat)));
m_vboEdge.release();
}
} else {
@ -197,6 +154,10 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
QOpenGLVertexArrayObject::Binder vaoBinder(&m_vaoEdge);
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
program->setUniformValue(program->textureEnabledLoc(), 0);
program->setUniformValue(program->normalMapEnabledLoc(), 0);
program->setUniformValue(program->metalnessMapEnabledLoc(), 0);
program->setUniformValue(program->roughnessMapEnabledLoc(), 0);
program->setUniformValue(program->ambientOcclusionMapEnabledLoc(), 0);
f->glDrawArrays(GL_LINES, 0, m_renderEdgeVertexCount);
}
}
@ -211,6 +172,22 @@ void ModelMeshBinder::paint(ModelShaderProgram *program)
} else {
program->setUniformValue(program->textureEnabledLoc(), 0);
}
if (m_hasNormalMap) {
if (m_normalMap)
m_normalMap->bind(1);
program->setUniformValue(program->normalMapIdLoc(), 1);
program->setUniformValue(program->normalMapEnabledLoc(), 1);
} else {
program->setUniformValue(program->normalMapEnabledLoc(), 0);
}
if (m_hasMetalnessMap || m_hasRoughnessMap || m_hasAmbientOcclusionMap) {
if (m_metalnessRoughnessAmbientOcclusionMap)
m_metalnessRoughnessAmbientOcclusionMap->bind(2);
program->setUniformValue(program->metalnessRoughnessAmbientOcclusionMapIdLoc(), 2);
}
program->setUniformValue(program->metalnessMapEnabledLoc(), m_hasMetalnessMap ? 1 : 0);
program->setUniformValue(program->roughnessMapEnabledLoc(), m_hasRoughnessMap ? 1 : 0);
program->setUniformValue(program->ambientOcclusionMapEnabledLoc(), m_hasAmbientOcclusionMap ? 1 : 0);
f->glDrawArrays(GL_TRIANGLES, 0, m_renderTriangleVertexCount);
}
}
@ -223,6 +200,10 @@ void ModelMeshBinder::cleanup()
m_vboEdge.destroy();
delete m_texture;
m_texture = nullptr;
delete m_normalMap;
m_normalMap = nullptr;
delete m_metalnessRoughnessAmbientOcclusionMap;
m_metalnessRoughnessAmbientOcclusionMap = nullptr;
}
void ModelMeshBinder::showWireframes()

View File

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

View File

@ -36,15 +36,23 @@ ModelShaderProgram::ModelShaderProgram(bool usePBR)
this->bindAttributeLocation("texCoord", 3);
this->bindAttributeLocation("metalness", 4);
this->bindAttributeLocation("roughness", 5);
this->bindAttributeLocation("tangent", 6);
this->link();
this->bind();
m_projectionMatrixLoc = this->uniformLocation("projectionMatrix");
m_modelMatrixLoc = this->uniformLocation("modelMatrix");
m_normalMatrixLoc = this->uniformLocation("normalMatrix");
m_viewMatrixLoc = this->uniformLocation("viewMatrix");
m_lightPosLoc = this->uniformLocation("lightPos");
m_textureIdLoc = this->uniformLocation("textureId");
m_textureEnabledLoc = this->uniformLocation("textureEnabled");
m_normalMapIdLoc = this->uniformLocation("normalMapId");
m_normalMapEnabledLoc = this->uniformLocation("normalMapEnabled");
m_metalnessMapEnabledLoc = this->uniformLocation("metalnessMapEnabled");
m_roughnessMapEnabledLoc = this->uniformLocation("roughnessMapEnabled");
m_ambientOcclusionMapEnabledLoc = this->uniformLocation("ambientOcclusionMapEnabled");
m_metalnessRoughnessAmbientOcclusionMapIdLoc = this->uniformLocation("metalnessRoughnessAmbientOcclusionMapId");
}
int ModelShaderProgram::projectionMatrixLoc()
@ -57,6 +65,11 @@ int ModelShaderProgram::modelMatrixLoc()
return m_modelMatrixLoc;
}
int ModelShaderProgram::normalMatrixLoc()
{
return m_normalMatrixLoc;
}
int ModelShaderProgram::viewMatrixLoc()
{
return m_viewMatrixLoc;
@ -76,3 +89,34 @@ int ModelShaderProgram::textureIdLoc()
{
return m_textureIdLoc;
}
int ModelShaderProgram::normalMapEnabledLoc()
{
return m_normalMapEnabledLoc;
}
int ModelShaderProgram::normalMapIdLoc()
{
return m_normalMapIdLoc;
}
int ModelShaderProgram::metalnessMapEnabledLoc()
{
return m_metalnessMapEnabledLoc;
}
int ModelShaderProgram::roughnessMapEnabledLoc()
{
return m_roughnessMapEnabledLoc;
}
int ModelShaderProgram::ambientOcclusionMapEnabledLoc()
{
return m_ambientOcclusionMapEnabledLoc;
}
int ModelShaderProgram::metalnessRoughnessAmbientOcclusionMapIdLoc()
{
return m_metalnessRoughnessAmbientOcclusionMapIdLoc;
}

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@
#include "skeletondocument.h"
#include "dust3dutil.h"
#include "skeletonxml.h"
#include "materialpreviewsgenerator.h"
unsigned long SkeletonDocument::m_maxSnapshot = 1000;
@ -54,7 +55,8 @@ SkeletonDocument::SkeletonDocument() :
m_isRigObsolete(false),
m_riggedResultContext(new MeshResultContext),
m_posePreviewsGenerator(nullptr),
m_currentRigSucceed(false)
m_currentRigSucceed(false),
m_materialPreviewsGenerator(nullptr)
{
}
@ -592,6 +594,14 @@ const SkeletonPose *SkeletonDocument::findPose(QUuid poseId) const
return &it->second;
}
const SkeletonMaterial *SkeletonDocument::findMaterial(QUuid materialId) const
{
auto it = materialMap.find(materialId);
if (it == materialMap.end())
return nullptr;
return &it->second;
}
const SkeletonMotion *SkeletonDocument::findMotion(QUuid motionId) const
{
auto it = motionMap.find(motionId);
@ -830,7 +840,10 @@ void SkeletonDocument::markAllDirty()
}
void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<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 ||
SkeletonDocumentToSnapshotFor::Nodes == forWhat) {
@ -874,10 +887,8 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
part["deformWidth"] = QString::number(partIt.second.deformWidth);
if (!partIt.second.name.isEmpty())
part["name"] = partIt.second.name;
if (partIt.second.metalnessAdjusted())
part["metalness"] = QString::number(partIt.second.metalness);
if (partIt.second.roughnessAdjusted())
part["roughness"] = QString::number(partIt.second.roughness);
if (partIt.second.materialAdjusted())
part["materialId"] = partIt.second.materialId.toString();
snapshot->parts[part["id"]] = part;
}
for (const auto &nodeIt: nodeMap) {
@ -952,6 +963,38 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
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 ||
SkeletonDocumentToSnapshotFor::Poses == forWhat) {
for (const auto &poseId: poseIdList) {
@ -1048,6 +1091,43 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
std::set<QUuid> inversePartIds;
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) {
const auto newUuid = QUuid::createUuid();
SkeletonPart &part = partMap[newUuid];
@ -1075,12 +1155,9 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
const auto &deformWidthIt = partKv.second.find("deformWidth");
if (deformWidthIt != partKv.second.end())
part.setDeformWidth(deformWidthIt->second.toFloat());
const auto &metalnessIt = partKv.second.find("metalness");
if (metalnessIt != partKv.second.end())
part.metalness = metalnessIt->second.toFloat();
const auto &roughnessIt = partKv.second.find("roughness");
if (roughnessIt != partKv.second.end())
part.roughness = roughnessIt->second.toFloat();
const auto &materialIdIt = partKv.second.find("materialId");
if (materialIdIt != partKv.second.end())
part.materialId = oldNewIdMap[QUuid(materialIdIt->second)];
newAddedPartIds.insert(part.id);
}
for (const auto &nodeKv: snapshot.nodes) {
@ -1263,6 +1340,8 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
emit checkEdge(edgeIt);
}
if (!snapshot.materials.empty())
emit materialListChanged();
if (!snapshot.poses.empty())
emit poseListChanged();
if (!snapshot.motions.empty())
@ -1279,6 +1358,8 @@ void SkeletonDocument::reset()
edgeMap.clear();
partMap.clear();
componentMap.clear();
materialMap.clear();
materialIdList.clear();
poseMap.clear();
poseIdList.clear();
motionMap.clear();
@ -1305,8 +1386,9 @@ MeshLoader *SkeletonDocument::takeResultMesh()
MeshLoader *SkeletonDocument::takeResultTextureMesh()
{
MeshLoader *resultTextureMesh = m_resultTextureMesh;
m_resultTextureMesh = nullptr;
if (nullptr == m_resultTextureMesh)
return nullptr;
MeshLoader *resultTextureMesh = new MeshLoader(*m_resultTextureMesh);
return resultTextureMesh;
}
@ -1413,20 +1495,21 @@ void SkeletonDocument::generateMesh()
SkeletonSnapshot *snapshot = new SkeletonSnapshot;
toSnapshot(snapshot);
resetDirtyFlags();
m_meshGenerator = new MeshGenerator(snapshot, thread);
m_meshGenerator = new MeshGenerator(snapshot);
m_meshGenerator->setSmoothNormal(m_smoothNormal);
m_meshGenerator->setWeldEnabled(weldEnabled);
m_meshGenerator->setGeneratedCacheContext(&m_generatedCacheContext);
if (nullptr != m_sharedContextWidget)
m_meshGenerator->setSharedContextWidget(m_sharedContextWidget);
m_meshGenerator->moveToThread(thread);
for (auto &part: partMap) {
m_meshGenerator->addPartPreviewRequirement(part.first);
}
m_meshGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_meshGenerator, &MeshGenerator::process);
connect(m_meshGenerator, &MeshGenerator::finished, this, &SkeletonDocument::meshReady);
connect(m_meshGenerator, &MeshGenerator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
emit meshGenerating();
thread->start();
}
@ -1442,12 +1525,31 @@ void SkeletonDocument::generateTexture()
m_isTextureObsolete = false;
QThread *thread = new QThread;
m_textureGenerator = new TextureGenerator(*m_postProcessedResultContext, thread);
m_textureGenerator = new TextureGenerator(*m_postProcessedResultContext);
for (const auto &bmeshNode: m_postProcessedResultContext->bmeshNodes) {
for (size_t i = 0; i < sizeof(bmeshNode.material.textureImages) / sizeof(bmeshNode.material.textureImages[0]); ++i) {
TextureType forWhat = (TextureType)(i + 1);
const QImage *image = bmeshNode.material.textureImages[i];
if (nullptr != image) {
if (TextureType::BaseColor == forWhat)
m_textureGenerator->addPartColorMap(bmeshNode.partId, image);
else if (TextureType::Normal == forWhat)
m_textureGenerator->addPartNormalMap(bmeshNode.partId, image);
else if (TextureType::Metalness == forWhat)
m_textureGenerator->addPartMetalnessMap(bmeshNode.partId, image);
else if (TextureType::Roughness == forWhat)
m_textureGenerator->addPartRoughnessMap(bmeshNode.partId, image);
else if (TextureType::AmbientOcclusion == forWhat)
m_textureGenerator->addPartAmbientOcclusionMap(bmeshNode.partId, image);
}
}
}
m_textureGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_textureGenerator, &TextureGenerator::process);
connect(m_textureGenerator, &TextureGenerator::finished, this, &SkeletonDocument::textureReady);
connect(m_textureGenerator, &TextureGenerator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
emit textureGenerating();
thread->start();
}
@ -1501,7 +1603,7 @@ void SkeletonDocument::bakeAmbientOcclusionTexture()
QThread *thread = new QThread;
m_ambientOcclusionBaker = new AmbientOcclusionBaker();
m_ambientOcclusionBaker->setInputMesh(*m_postProcessedResultContext);
m_ambientOcclusionBaker->setBakeSize(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight);
m_ambientOcclusionBaker->setBakeSize(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize);
if (textureBorderImage)
m_ambientOcclusionBaker->setBorderImage(*textureBorderImage);
if (textureColorImage)
@ -1578,6 +1680,7 @@ void SkeletonDocument::postProcess()
connect(m_postProcessor, &MeshResultPostProcessor::finished, this, &SkeletonDocument::postProcessedMeshResultReady);
connect(m_postProcessor, &MeshResultPostProcessor::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
emit postProcessing();
thread->start();
}
@ -2155,29 +2258,18 @@ void SkeletonDocument::setPartDeformWidth(QUuid partId, float width)
emit skeletonChanged();
}
void SkeletonDocument::setPartMetalness(QUuid partId, float metalness)
void SkeletonDocument::setPartMaterialId(QUuid partId, QUuid materialId)
{
auto part = partMap.find(partId);
if (part == partMap.end()) {
qDebug() << "Part not found:" << partId;
return;
}
part->second.metalness = metalness;
part->second.dirty = true;
emit partMetalnessChanged(partId);
emit skeletonChanged();
}
void SkeletonDocument::setPartRoughness(QUuid partId, float roughness)
{
auto part = partMap.find(partId);
if (part == partMap.end()) {
qDebug() << "Part not found:" << partId;
if (part->second.materialId == materialId)
return;
}
part->second.roughness = roughness;
part->second.materialId = materialId;
part->second.dirty = true;
emit partRoughnessChanged(partId);
emit partMaterialIdChanged(partId);
emit skeletonChanged();
}
@ -2280,6 +2372,17 @@ bool SkeletonDocument::hasPastableNodesInClipboard() const
return false;
}
bool SkeletonDocument::hasPastableMaterialsInClipboard() const
{
const QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
if (mimeData->hasText()) {
if (-1 != mimeData->text().indexOf("<material "))
return true;
}
return false;
}
bool SkeletonDocument::hasPastablePosesInClipboard() const
{
const QClipboard *clipboard = QApplication::clipboard();
@ -2741,3 +2844,116 @@ void SkeletonDocument::posePreviewsReady()
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 "posepreviewsgenerator.h"
#include "curveutil.h"
#include "texturetype.h"
class MaterialPreviewsGenerator;
class SkeletonNode
{
@ -97,8 +100,7 @@ public:
std::vector<QUuid> nodeIds;
bool dirty;
bool wrapped;
float metalness;
float roughness;
QUuid materialId;
SkeletonPart(const QUuid &withId=QUuid()) :
visible(true),
locked(false),
@ -112,9 +114,7 @@ public:
color(Theme::white),
hasColor(false),
dirty(true),
wrapped(false),
metalness(0.0),
roughness(1.0)
wrapped(false)
{
id = withId.isNull() ? QUuid::createUuid() : withId;
}
@ -146,17 +146,9 @@ public:
{
return deformThicknessAdjusted() || deformWidthAdjusted();
}
bool metalnessAdjusted() const
{
return fabs(metalness - 0.0) >= 0.01;
}
bool roughnessAdjusted() const
{
return fabs(roughness - 1.0) >= 0.01;
}
bool materialAdjusted() const
{
return metalnessAdjusted() || roughnessAdjusted();
return !materialId.isNull();
}
bool isEditVisible() const
{
@ -178,8 +170,7 @@ public:
wrapped = other.wrapped;
componentId = other.componentId;
dirty = other.dirty;
metalness = other.metalness;
roughness = other.roughness;
materialId = other.materialId;
}
void updatePreviewMesh(MeshLoader *previewMesh)
{
@ -411,10 +402,54 @@ private:
Q_DISABLE_COPY(SkeletonMotion);
};
class SkeletonMaterialMap
{
public:
TextureType forWhat;
QUuid imageId;
};
class SkeletonMaterialLayer
{
public:
std::vector<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
{
Document = 0,
Nodes,
Materials,
Poses,
Motions
};
@ -462,8 +497,7 @@ signals:
void partRoundStateChanged(QUuid partId);
void partColorStateChanged(QUuid partId);
void partWrapStateChanged(QUuid partId);
void partMetalnessChanged(QUuid partId);
void partRoughnessChanged(QUuid partId);
void partMaterialIdChanged(QUuid partId);
void componentInverseStateChanged(QUuid partId);
void cleanup();
void originChanged();
@ -494,6 +528,15 @@ signals:
void motionNameChanged(QUuid motionId);
void motionControlNodesChanged(QUuid motionId);
void motionKeyframesChanged(QUuid motionId);
void materialAdded(QUuid materialId);
void materialRemoved(QUuid materialId);
void materialListChanged();
void materialNameChanged(QUuid materialId);
void materialLayersChanged(QUuid materialId);
void materialPreviewChanged(QUuid materialId);
void meshGenerating();
void postProcessing();
void textureGenerating();
public: // need initialize
float originX;
float originY;
@ -517,6 +560,8 @@ public:
std::map<QUuid, SkeletonNode> nodeMap;
std::map<QUuid, SkeletonEdge> edgeMap;
std::map<QUuid, SkeletonComponent> componentMap;
std::map<QUuid, SkeletonMaterial> materialMap;
std::vector<QUuid> materialIdList;
std::map<QUuid, SkeletonPose> poseMap;
std::vector<QUuid> poseIdList;
std::map<QUuid, SkeletonMotion> motionMap;
@ -527,7 +572,8 @@ public:
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(),
SkeletonDocumentToSnapshotFor forWhat=SkeletonDocumentToSnapshotFor::Document,
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 addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true);
const SkeletonNode *findNode(QUuid nodeId) const;
@ -537,6 +583,7 @@ public:
const SkeletonComponent *findComponent(QUuid componentId) const;
const SkeletonComponent *findComponentParent(QUuid componentId) const;
QUuid findComponentParentId(QUuid componentId) const;
const SkeletonMaterial *findMaterial(QUuid materialId) const;
const SkeletonPose *findPose(QUuid poseId) const;
const SkeletonMotion *findMotion(QUuid motionId) const;
MeshLoader *takeResultMesh();
@ -547,6 +594,7 @@ public:
void updateTurnaround(const QImage &image);
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
bool hasPastableNodesInClipboard() const;
bool hasPastableMaterialsInClipboard() const;
bool hasPastablePosesInClipboard() const;
bool hasPastableMotionsInClipboard() const;
bool undoable() const;
@ -592,6 +640,8 @@ public slots:
void rigReady();
void generatePosePreviews();
void posePreviewsReady();
void generateMaterialPreviews();
void materialPreviewsReady();
void setPartLockState(QUuid partId, bool locked);
void setPartVisibleState(QUuid partId, bool visible);
void setPartSubdivState(QUuid partId, bool subdived);
@ -603,8 +653,7 @@ public slots:
void setPartRoundState(QUuid partId, bool rounded);
void setPartColorState(QUuid partId, bool hasColor, QColor color);
void setPartWrapState(QUuid partId, bool wrapped);
void setPartMetalness(QUuid partId, float metalness);
void setPartRoughness(QUuid partId, float roughness);
void setPartMaterialId(QUuid partId, QUuid materialId);
void setComponentInverseState(QUuid componentId, bool inverse);
void moveComponentUp(QUuid componentId);
void moveComponentDown(QUuid componentId);
@ -658,6 +707,10 @@ public slots:
void setMotionControlNodes(QUuid motionId, std::vector<HermiteControlNode> controlNodes);
void setMotionKeyframes(QUuid motionId, std::vector<std::pair<float, QUuid>> keyframes);
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:
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());
@ -700,6 +753,7 @@ private: // need initialize
MeshResultContext *m_riggedResultContext;
PosePreviewsGenerator *m_posePreviewsGenerator;
bool m_currentRigSucceed;
MaterialPreviewsGenerator *m_materialPreviewsGenerator;
private:
static unsigned long m_maxSnapshot;
std::deque<SkeletonHistoryItem> m_undoItems;

View File

@ -31,6 +31,9 @@
#include "rigwidget.h"
#include "markiconcreator.h"
#include "motionmanagewidget.h"
#include "materialmanagewidget.h"
#include "imageforever.h"
#include "spinnableawesomebutton.h"
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
@ -151,6 +154,22 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
QPushButton *rotateClockwiseButton = new QPushButton(QChar(fa::rotateright));
Theme::initAwesomeButton(rotateClockwiseButton);
SpinnableAwesomeButton *regenerateButton = new SpinnableAwesomeButton();
regenerateButton->setAwesomeIcon(QChar(fa::recycle));
connect(m_document, &SkeletonDocument::meshGenerating, this, [=]() {
regenerateButton->showSpinner(true);
});
connect(m_document, &SkeletonDocument::postProcessing, this, [=]() {
regenerateButton->showSpinner(true);
});
connect(m_document, &SkeletonDocument::textureGenerating, this, [=]() {
regenerateButton->showSpinner(true);
});
connect(m_document, &SkeletonDocument::resultTextureChanged, this, [=]() {
regenerateButton->showSpinner(false);
});
connect(regenerateButton->button(), &QPushButton::clicked, m_document, &SkeletonDocument::regenerateMesh);
toolButtonLayout->addWidget(addButton);
toolButtonLayout->addWidget(selectButton);
toolButtonLayout->addWidget(dragButton);
@ -164,6 +183,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
toolButtonLayout->addSpacing(10);
toolButtonLayout->addWidget(rotateCounterclockwiseButton);
toolButtonLayout->addWidget(rotateClockwiseButton);
toolButtonLayout->addSpacing(10);
toolButtonLayout->addWidget(regenerateButton);
QLabel *verticalLogoLabel = new QLabel;
QImage verticalLogoImage;
@ -216,6 +238,19 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
partTreeWidget->partPreviewChanged(part.first);
});
QDockWidget *materialDocker = new QDockWidget(tr("Materials"), this);
materialDocker->setAllowedAreas(Qt::RightDockWidgetArea);
MaterialManageWidget *materialManageWidget = new MaterialManageWidget(m_document, materialDocker);
materialDocker->setWidget(materialManageWidget);
connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog);
connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
addDockWidget(Qt::RightDockWidgetArea, materialDocker);
connect(materialDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) {
Q_UNUSED(topLevel);
for (const auto &material: m_document->materialMap)
emit m_document->materialPreviewChanged(material.first);
});
QDockWidget *rigDocker = new QDockWidget(tr("Rig"), this);
rigDocker->setAllowedAreas(Qt::RightDockWidgetArea);
m_rigWidget = new RigWidget(m_document, rigDocker);
@ -247,7 +282,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
addDockWidget(Qt::RightDockWidgetArea, motionDocker);
tabifyDockWidget(partTreeDocker, rigDocker);
tabifyDockWidget(partTreeDocker, materialDocker);
tabifyDockWidget(materialDocker, rigDocker);
tabifyDockWidget(rigDocker, poseDocker);
tabifyDockWidget(poseDocker, motionDocker);
@ -495,17 +531,17 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
});
m_viewMenu->addAction(m_resetModelWidgetPosAction);
m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this);
connect(m_toggleWireframeAction, &QAction::triggered, [=]() {
m_modelRenderWidget->toggleWireframe();
});
m_viewMenu->addAction(m_toggleWireframeAction);
//m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this);
//connect(m_toggleWireframeAction, &QAction::triggered, [=]() {
// m_modelRenderWidget->toggleWireframe();
//});
//m_viewMenu->addAction(m_toggleWireframeAction);
m_toggleSmoothNormalAction = new QAction(tr("Toggle Smooth Normal"), this);
connect(m_toggleSmoothNormalAction, &QAction::triggered, [=]() {
m_document->toggleSmoothNormal();
});
m_viewMenu->addAction(m_toggleSmoothNormalAction);
//m_toggleSmoothNormalAction = new QAction(tr("Toggle Smooth Normal"), this);
//connect(m_toggleSmoothNormalAction, &QAction::triggered, [=]() {
// m_document->toggleSmoothNormal();
//});
//m_viewMenu->addAction(m_toggleSmoothNormalAction);
connect(m_viewMenu, &QMenu::aboutToShow, [=]() {
m_resetModelWidgetPosAction->setEnabled(!isModelSitInVisibleArea(m_modelRenderWidget));
@ -520,6 +556,13 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
});
m_windowMenu->addAction(m_showPartsListAction);
m_showMaterialsAction = new QAction(tr("Materials"), this);
connect(m_showMaterialsAction, &QAction::triggered, [=]() {
materialDocker->show();
materialDocker->raise();
});
m_windowMenu->addAction(m_showMaterialsAction);
m_showRigAction = new QAction(tr("Rig"), this);
connect(m_showRigAction, &QAction::triggered, [=]() {
rigDocker->show();
@ -760,30 +803,30 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(m_document, &SkeletonDocument::partRoundStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoundStateChanged);
connect(m_document, &SkeletonDocument::partWrapStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partWrapStateChanged);
connect(m_document, &SkeletonDocument::partColorStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partColorStateChanged);
connect(m_document, &SkeletonDocument::partMetalnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partMetalnessChanged);
connect(m_document, &SkeletonDocument::partRoughnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoughnessChanged);
connect(m_document, &SkeletonDocument::partMaterialIdChanged, partTreeWidget, &SkeletonPartTreeWidget::partMaterialIdChanged);
connect(m_document, &SkeletonDocument::partRemoved, partTreeWidget, &SkeletonPartTreeWidget::partRemoved);
connect(m_document, &SkeletonDocument::cleanup, partTreeWidget, &SkeletonPartTreeWidget::removeAllContent);
connect(m_document, &SkeletonDocument::partChecked, partTreeWidget, &SkeletonPartTreeWidget::partChecked);
connect(m_document, &SkeletonDocument::partUnchecked, partTreeWidget, &SkeletonPartTreeWidget::partUnchecked);
connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) {
m_document->postProcess();
}
});
//connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
// if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) {
// m_document->postProcess();
// }
//});
connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::postProcess);
connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::generateRig);
connect(m_document, &SkeletonDocument::rigChanged, m_document, &SkeletonDocument::generateRig);
connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateTexture);
connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture);
//connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture);
connect(m_document, &SkeletonDocument::resultTextureChanged, [=]() {
m_modelRenderWidget->updateMesh(m_document->takeResultTextureMesh());
});
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
m_modelRenderWidget->updateMesh(m_document->takeResultMesh());
});
//connect(m_document, &SkeletonDocument::resultSkeletonChanged, [=]() {
// m_skeletonRenderWidget->updateMesh(m_document->takeResultSkeletonMesh());
//});
connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() {
m_modelRenderWidget->setCursor(graphicsWidget->cursor());
@ -822,6 +865,15 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
});
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);
QTimer *timer = new QTimer(this);
@ -1038,6 +1090,26 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
ds3Writer.add("canvas.png", "asset", &imageByteArray);
}
for (auto &material: snapshot.materials) {
for (auto &layer: material.second) {
for (auto &mapItem: layer.second) {
auto findImageIdString = mapItem.find("linkData");
if (findImageIdString == mapItem.end())
continue;
QUuid imageId = QUuid(findImageIdString->second);
const QImage *image = ImageForever::get(imageId);
if (nullptr == image)
continue;
QByteArray imageByteArray;
QBuffer pngBuffer(&imageByteArray);
pngBuffer.open(QIODevice::WriteOnly);
image->save(&pngBuffer, "PNG");
if (imageByteArray.size() > 0)
ds3Writer.add("images/" + imageId.toString() + ".png", "asset", &imageByteArray);
}
}
}
if (ds3Writer.save(filename)) {
setCurrentFilename(filename);
}
@ -1063,6 +1135,24 @@ void SkeletonDocumentWindow::open()
QApplication::setOverrideCursor(Qt::WaitCursor);
Ds3FileReader ds3Reader(filename);
for (int i = 0; i < ds3Reader.items().size(); ++i) {
Ds3ReaderItem item = ds3Reader.items().at(i);
if (item.type == "asset") {
if (item.name.startsWith("images/")) {
QString filename = item.name.split("/")[1];
QString imageIdString = filename.split(".")[0];
QUuid imageId = QUuid(imageIdString);
if (!imageId.isNull()) {
QByteArray data;
ds3Reader.loadItem(item.name, &data);
QImage image = QImage::fromData(data, "PNG");
(void)ImageForever::add(&image, imageId);
}
}
}
}
for (int i = 0; i < ds3Reader.items().size(); ++i) {
Ds3ReaderItem item = ds3Reader.items().at(i);
if (item.type == "model") {
@ -1104,19 +1194,11 @@ void SkeletonDocumentWindow::exportObjResult()
return;
}
QApplication::setOverrideCursor(Qt::WaitCursor);
m_modelRenderWidget->exportMeshAsObj(filename);
QApplication::restoreOverrideCursor();
MeshLoader *resultMesh = m_document->takeResultMesh();
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();
}

View File

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

View File

@ -932,18 +932,7 @@ void SkeletonPartTreeWidget::partColorStateChanged(QUuid partId)
widget->updateColorButton();
}
void SkeletonPartTreeWidget::partMetalnessChanged(QUuid partId)
{
auto item = m_partItemMap.find(partId);
if (item == m_partItemMap.end()) {
qDebug() << "Part item not found:" << partId;
return;
}
SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second, 0);
widget->updateColorButton();
}
void SkeletonPartTreeWidget::partRoughnessChanged(QUuid partId)
void SkeletonPartTreeWidget::partMaterialIdChanged(QUuid partId)
{
auto item = m_partItemMap.find(partId);
if (item == m_partItemMap.end()) {

View File

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

View File

@ -5,9 +5,12 @@
#include <QWidgetAction>
#include <QColorDialog>
#include <QSizePolicy>
#include <QFileDialog>
#include "skeletonpartwidget.h"
#include "theme.h"
#include "floatnumberwidget.h"
#include "materiallistwidget.h"
#include "infolabel.h"
SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid partId) :
m_document(document),
@ -131,8 +134,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
connect(this, &SkeletonPartWidget::setPartRoundState, m_document, &SkeletonDocument::setPartRoundState);
connect(this, &SkeletonPartWidget::setPartWrapState, m_document, &SkeletonDocument::setPartWrapState);
connect(this, &SkeletonPartWidget::setPartColorState, m_document, &SkeletonDocument::setPartColorState);
connect(this, &SkeletonPartWidget::setPartMetalness, m_document, &SkeletonDocument::setPartMetalness);
connect(this, &SkeletonPartWidget::setPartRoughness, m_document, &SkeletonDocument::setPartRoughness);
connect(this, &SkeletonPartWidget::setPartMaterialId, m_document, &SkeletonDocument::setPartMaterialId);
connect(this, &SkeletonPartWidget::checkPart, m_document, &SkeletonDocument::checkPart);
connect(this, &SkeletonPartWidget::enableBackgroundBlur, m_document, &SkeletonDocument::enableBackgroundBlur);
connect(this, &SkeletonPartWidget::disableBackgroundBlur, m_document, &SkeletonDocument::disableBackgroundBlur);
@ -323,53 +325,23 @@ void SkeletonPartWidget::showColorSettingPopup(const QPoint &pos)
}
});
FloatNumberWidget *metalnessWidget = new FloatNumberWidget;
metalnessWidget->setItemName(tr("Metalness"));
metalnessWidget->setRange(0, 1);
metalnessWidget->setValue(part->metalness);
connect(metalnessWidget, &FloatNumberWidget::valueChanged, [=](float value) {
emit setPartMetalness(m_partId, value);
emit groupOperationAdded();
});
FloatNumberWidget *roughnessWidget = new FloatNumberWidget;
roughnessWidget->setItemName(tr("Roughness"));
roughnessWidget->setRange(0, 1);
roughnessWidget->setValue(part->roughness);
connect(roughnessWidget, &FloatNumberWidget::valueChanged, [=](float value) {
emit setPartRoughness(m_partId, value);
emit groupOperationAdded();
});
QPushButton *metalnessEraser = new QPushButton(QChar(fa::eraser));
initToolButton(metalnessEraser);
connect(metalnessEraser, &QPushButton::clicked, [=]() {
metalnessWidget->setValue(0.0);
emit groupOperationAdded();
});
QPushButton *roughnessEraser = new QPushButton(QChar(fa::eraser));
initToolButton(roughnessEraser);
connect(roughnessEraser, &QPushButton::clicked, [=]() {
roughnessWidget->setValue(1.0);
emit groupOperationAdded();
});
QHBoxLayout *metalnessLayout = new QHBoxLayout;
QHBoxLayout *roughnessLayout = new QHBoxLayout;
metalnessLayout->addWidget(metalnessEraser);
metalnessLayout->addWidget(metalnessWidget);
roughnessLayout->addWidget(roughnessEraser);
roughnessLayout->addWidget(roughnessWidget);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(colorLayout);
mainLayout->addLayout(metalnessLayout);
mainLayout->addLayout(roughnessLayout);
if (m_document->materialIdList.empty()) {
InfoLabel *infoLabel = new InfoLabel;
infoLabel->setText(tr("Missing Materials"));
mainLayout->addWidget(infoLabel);
} else {
MaterialListWidget *materialListWidget = new MaterialListWidget(m_document);
materialListWidget->enableMultipleSelection(false);
materialListWidget->selectMaterial(part->materialId);
connect(materialListWidget, &MaterialListWidget::currentSelectedMaterialChanged, this, [=](QUuid materialId) {
emit setPartMaterialId(m_partId, materialId);
emit groupOperationAdded();
});
mainLayout->addWidget(materialListWidget);
}
popup->setLayout(mainLayout);

View File

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

View File

@ -17,6 +17,7 @@ public:
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::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:
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->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");
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++) {
@ -165,6 +200,8 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
{
std::stack<QString> componentStack;
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::tuple<std::map<QString, QString>, std::vector<std::map<QString, QString>>, std::vector<std::map<QString, QString>>> currentMotion;
while (!reader.atEnd()) {
@ -243,6 +280,25 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
if (!parentChildrenIds.isEmpty())
parentChildrenIds += ",";
parentChildrenIds += componentId;
} else if (fullName == "canvas.materials.material.layers.layer") {
currentMaterialLayer = decltype(currentMaterialLayer)();
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
currentMaterialLayer.first[attr.name().toString()] = attr.value().toString();
}
} else if (fullName == "canvas.materials.material.layers.layer.maps.map") {
std::map<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") {
QString poseId = reader.attributes().value("id").toString();
if (poseId.isEmpty())
@ -285,6 +341,10 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
} else if (reader.isEndElement()) {
if (fullName.startsWith("canvas.components.component")) {
componentStack.pop();
} else if (fullName == "canvas.materials.material.layers.layer") {
currentMaterial.second.push_back(currentMaterialLayer);
} else if (fullName == "canvas.materials.material") {
snapshot->materials.push_back(currentMaterial);
} else if (fullName == "canvas.poses.pose") {
snapshot->poses.push_back(currentPose);
} else if (fullName == "canvas.motions.motion") {

View File

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

View File

@ -1,17 +1,22 @@
#include <QPainter>
#include <QGuiApplication>
#include <QRegion>
#include <QPolygon>
#include "texturegenerator.h"
#include "theme.h"
int TextureGenerator::m_textureWidth = 512;
int TextureGenerator::m_textureHeight = 512;
int TextureGenerator::m_textureSize = 1024;
TextureGenerator::TextureGenerator(const MeshResultContext &meshResultContext, QThread *thread) :
TextureGenerator::TextureGenerator(const MeshResultContext &meshResultContext) :
m_resultTextureGuideImage(nullptr),
m_resultTextureImage(nullptr),
m_resultTextureBorderImage(nullptr),
m_resultTextureColorImage(nullptr),
m_thread(thread),
m_resultTextureNormalImage(nullptr),
m_resultTextureMetalnessRoughnessAmbientOcclusionImage(nullptr),
m_resultTextureRoughnessImage(nullptr),
m_resultTextureMetalnessImage(nullptr),
m_resultTextureAmbientOcclusionImage(nullptr),
m_resultMesh(nullptr)
{
m_resultContext = new MeshResultContext();
@ -25,6 +30,11 @@ TextureGenerator::~TextureGenerator()
delete m_resultTextureImage;
delete m_resultTextureBorderImage;
delete m_resultTextureColorImage;
delete m_resultTextureNormalImage;
delete m_resultTextureMetalnessRoughnessAmbientOcclusionImage;
delete m_resultTextureRoughnessImage;
delete m_resultTextureMetalnessImage;
delete m_resultTextureAmbientOcclusionImage;
delete m_resultMesh;
}
@ -56,6 +66,13 @@ QImage *TextureGenerator::takeResultTextureColorImage()
return resultTextureColorImage;
}
QImage *TextureGenerator::takeResultTextureNormalImage()
{
QImage *resultTextureNormalImage = m_resultTextureNormalImage;
m_resultTextureNormalImage = nullptr;
return resultTextureNormalImage;
}
MeshResultContext *TextureGenerator::takeResultContext()
{
MeshResultContext *resultContext = m_resultContext;
@ -70,17 +87,80 @@ MeshLoader *TextureGenerator::takeResultMesh()
return resultMesh;
}
void TextureGenerator::process()
void TextureGenerator::addPartColorMap(QUuid partId, const QImage *image)
{
if (nullptr == image)
return;
m_partColorTextureMap[partId] = *image;
}
void TextureGenerator::addPartNormalMap(QUuid partId, const QImage *image)
{
if (nullptr == image)
return;
m_partNormalTextureMap[partId] = *image;
}
void TextureGenerator::addPartMetalnessMap(QUuid partId, const QImage *image)
{
if (nullptr == image)
return;
m_partMetalnessTextureMap[partId] = *image;
}
void TextureGenerator::addPartRoughnessMap(QUuid partId, const QImage *image)
{
if (nullptr == image)
return;
m_partRoughnessTextureMap[partId] = *image;
}
void TextureGenerator::addPartAmbientOcclusionMap(QUuid partId, const QImage *image)
{
if (nullptr == image)
return;
m_partAmbientOcclusionTextureMap[partId] = *image;
}
QPainterPath TextureGenerator::expandedPainterPath(const QPainterPath &painterPath)
{
QPainterPathStroker stroker;
stroker.setWidth(20);
stroker.setJoinStyle(Qt::MiterJoin);
return (stroker.createStroke(painterPath) + painterPath).simplified();
}
void TextureGenerator::generate()
{
bool hasNormalMap = false;
bool hasMetalnessMap = false;
bool hasRoughnessMap = false;
bool hasAmbientOcclusionMap = false;
const std::vector<Material> &triangleMaterials = m_resultContext->triangleMaterials();
const std::vector<ResultTriangleUv> &triangleUvs = m_resultContext->triangleUvs();
m_resultTextureColorImage = new QImage(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight, QImage::Format_ARGB32);
m_resultTextureColorImage->fill(Qt::transparent);
m_resultTextureColorImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureColorImage->fill(Theme::white);
m_resultTextureBorderImage = new QImage(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight, QImage::Format_ARGB32);
m_resultTextureBorderImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureBorderImage->fill(Qt::transparent);
m_resultTextureNormalImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureNormalImage->fill(Qt::transparent);
m_resultTextureMetalnessRoughnessAmbientOcclusionImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureMetalnessRoughnessAmbientOcclusionImage->fill(Qt::transparent);
m_resultTextureMetalnessImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureMetalnessImage->fill(Qt::transparent);
m_resultTextureRoughnessImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureRoughnessImage->fill(Qt::transparent);
m_resultTextureAmbientOcclusionImage = new QImage(TextureGenerator::m_textureSize, TextureGenerator::m_textureSize, QImage::Format_ARGB32);
m_resultTextureAmbientOcclusionImage->fill(Qt::transparent);
QColor borderColor = Qt::darkGray;
QPen pen(borderColor);
@ -94,17 +174,38 @@ void TextureGenerator::process()
textureBorderPainter.setRenderHint(QPainter::Antialiasing);
textureBorderPainter.setRenderHint(QPainter::HighQualityAntialiasing);
QPainter textureNormalPainter;
textureNormalPainter.begin(m_resultTextureNormalImage);
textureNormalPainter.setRenderHint(QPainter::Antialiasing);
textureNormalPainter.setRenderHint(QPainter::HighQualityAntialiasing);
QPainter textureMetalnessPainter;
textureMetalnessPainter.begin(m_resultTextureMetalnessImage);
textureMetalnessPainter.setRenderHint(QPainter::Antialiasing);
textureMetalnessPainter.setRenderHint(QPainter::HighQualityAntialiasing);
QPainter textureRoughnessPainter;
textureRoughnessPainter.begin(m_resultTextureRoughnessImage);
textureRoughnessPainter.setRenderHint(QPainter::Antialiasing);
textureRoughnessPainter.setRenderHint(QPainter::HighQualityAntialiasing);
QPainter textureAmbientOcclusionPainter;
textureAmbientOcclusionPainter.begin(m_resultTextureAmbientOcclusionImage);
textureAmbientOcclusionPainter.setRenderHint(QPainter::Antialiasing);
textureAmbientOcclusionPainter.setRenderHint(QPainter::HighQualityAntialiasing);
// round 1, paint background
for (auto i = 0u; i < triangleUvs.size(); i++) {
QPainterPath path;
const ResultTriangleUv *uv = &triangleUvs[i];
for (auto j = 0; j < 3; j++) {
if (0 == j) {
path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight);
path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureSize, uv->uv[j][1] * TextureGenerator::m_textureSize);
} else {
path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight);
path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureSize, uv->uv[j][1] * TextureGenerator::m_textureSize);
}
}
path = expandedPainterPath(path);
QPen textureBorderPen(triangleMaterials[i].color);
textureBorderPen.setWidth(32);
texturePainter.setPen(textureBorderPen);
@ -116,14 +217,82 @@ void TextureGenerator::process()
for (auto i = 0u; i < triangleUvs.size(); i++) {
QPainterPath path;
const ResultTriangleUv *uv = &triangleUvs[i];
for (auto j = 0; j < 3; j++) {
if (0 == j) {
path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight);
} else {
path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight);
}
}
float points[][2] = {
{uv->uv[0][0] * TextureGenerator::m_textureSize, uv->uv[0][1] * TextureGenerator::m_textureSize},
{uv->uv[1][0] * TextureGenerator::m_textureSize, uv->uv[1][1] * TextureGenerator::m_textureSize},
{uv->uv[2][0] * TextureGenerator::m_textureSize, uv->uv[2][1] * TextureGenerator::m_textureSize}
};
path.moveTo(points[0][0], points[0][1]);
path.lineTo(points[1][0], points[1][1]);
path.lineTo(points[2][0], points[2][1]);
path = expandedPainterPath(path);
// Fill base color
texturePainter.fillPath(path, QBrush(triangleMaterials[i].color));
// Copy color texture if there is one
const std::pair<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);
@ -133,13 +302,40 @@ void TextureGenerator::process()
for (auto j = 0; j < 3; j++) {
int from = j;
int to = (j + 1) % 3;
textureBorderPainter.drawLine(uv->uv[from][0] * TextureGenerator::m_textureWidth, uv->uv[from][1] * TextureGenerator::m_textureHeight,
uv->uv[to][0] * TextureGenerator::m_textureWidth, uv->uv[to][1] * TextureGenerator::m_textureHeight);
textureBorderPainter.drawLine(uv->uv[from][0] * TextureGenerator::m_textureSize, uv->uv[from][1] * TextureGenerator::m_textureSize,
uv->uv[to][0] * TextureGenerator::m_textureSize, uv->uv[to][1] * TextureGenerator::m_textureSize);
}
}
texturePainter.end();
textureBorderPainter.end();
textureNormalPainter.end();
textureMetalnessPainter.end();
textureRoughnessPainter.end();
textureAmbientOcclusionPainter.end();
if (!hasNormalMap) {
delete m_resultTextureNormalImage;
m_resultTextureNormalImage = nullptr;
}
if (!hasMetalnessMap && !hasRoughnessMap && !hasAmbientOcclusionMap) {
delete m_resultTextureMetalnessRoughnessAmbientOcclusionImage;
m_resultTextureMetalnessRoughnessAmbientOcclusionImage = nullptr;
} else {
for (int row = 0; row < m_resultTextureMetalnessRoughnessAmbientOcclusionImage->height(); ++row) {
for (int col = 0; col < m_resultTextureMetalnessRoughnessAmbientOcclusionImage->width(); ++col) {
QColor color;
if (hasMetalnessMap)
color.setBlue(qGray(m_resultTextureMetalnessImage->pixel(col, row)));
if (hasRoughnessMap)
color.setRed(qGray(m_resultTextureRoughnessImage->pixel(col, row)));
if (hasAmbientOcclusionMap)
color.setGreen(qGray(m_resultTextureAmbientOcclusionImage->pixel(col, row)));
m_resultTextureMetalnessRoughnessAmbientOcclusionImage->setPixelColor(col, row, color);
}
}
}
m_resultTextureImage = new QImage(*m_resultTextureColorImage);
@ -151,8 +347,20 @@ void TextureGenerator::process()
m_resultMesh = new MeshLoader(*m_resultContext);
m_resultMesh->setTextureImage(new QImage(*m_resultTextureImage));
if (nullptr != m_resultTextureNormalImage)
m_resultMesh->setNormalMapImage(new QImage(*m_resultTextureNormalImage));
if (nullptr != m_resultTextureMetalnessRoughnessAmbientOcclusionImage) {
m_resultMesh->setMetalnessRoughnessAmbientOcclusionImage(new QImage(*m_resultTextureMetalnessRoughnessAmbientOcclusionImage));
m_resultMesh->setHasMetalnessInImage(hasMetalnessMap);
m_resultMesh->setHasRoughnessInImage(hasRoughnessMap);
m_resultMesh->setHasAmbientOcclusionInImage(hasAmbientOcclusionMap);
}
}
void TextureGenerator::process()
{
generate();
this->moveToThread(QGuiApplication::instance()->thread());
emit finished();
}

View File

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

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::miniIconSize = 15;
int Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
int Theme::materialPreviewImageSize = 75;
int Theme::posePreviewImageSize = 75;
int Theme::motionPreviewImageSize = 75;
int Theme::sidebarPreferredWidth = 200;

View File

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