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
parent
308bc48c54
commit
7eb5a325a0
21
dust3d.pro
21
dust3d.pro
|
@ -224,6 +224,27 @@ HEADERS += src/motionpreviewsgenerator.h
|
|||
SOURCES += src/animationclipplayer.cpp
|
||||
HEADERS += src/animationclipplayer.h
|
||||
|
||||
SOURCES += src/texturetype.cpp
|
||||
HEADERS += src/texturetype.h
|
||||
|
||||
SOURCES += src/imageforever.cpp
|
||||
HEADERS += src/imageforever.h
|
||||
|
||||
SOURCES += src/materialeditwidget.cpp
|
||||
HEADERS += src/materialeditwidget.h
|
||||
|
||||
SOURCES += src/materiallistwidget.cpp
|
||||
HEADERS += src/materiallistwidget.h
|
||||
|
||||
SOURCES += src/materialmanagewidget.cpp
|
||||
HEADERS += src/materialmanagewidget.h
|
||||
|
||||
SOURCES += src/materialpreviewsgenerator.cpp
|
||||
HEADERS += src/materialpreviewsgenerator.h
|
||||
|
||||
SOURCES += src/materialwidget.cpp
|
||||
HEADERS += src/materialwidget.h
|
||||
|
||||
SOURCES += src/main.cpp
|
||||
|
||||
HEADERS += src/version.h
|
||||
|
|
|
@ -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.
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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++;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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(©Action, &QAction::triggered, this, &MaterialListWidget::copy);
|
||||
contextMenu.addAction(©Action);
|
||||
}
|
||||
|
||||
QAction pasteAction(tr("Paste"), this);
|
||||
if (m_document->hasPastableMaterialsInClipboard()) {
|
||||
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
|
||||
contextMenu.addAction(&pasteAction);
|
||||
}
|
||||
|
||||
QAction deleteAction(tr("Delete"), this);
|
||||
if (!materialIds.empty()) {
|
||||
connect(&deleteAction, &QAction::triggered, [=]() {
|
||||
for (const auto &materialId: materialIds)
|
||||
emit removeMaterial(materialId);
|
||||
});
|
||||
contextMenu.addAction(&deleteAction);
|
||||
}
|
||||
|
||||
contextMenu.exec(mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void MaterialListWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QTreeWidget::resizeEvent(event);
|
||||
if (calculateColumnCount() != columnCount())
|
||||
reload();
|
||||
}
|
||||
|
||||
int MaterialListWidget::calculateColumnCount()
|
||||
{
|
||||
if (nullptr == parentWidget())
|
||||
return 0;
|
||||
|
||||
int columns = parentWidget()->width() / Theme::materialPreviewImageSize;
|
||||
if (0 == columns)
|
||||
columns = 1;
|
||||
return columns;
|
||||
}
|
||||
|
||||
void MaterialListWidget::reload()
|
||||
{
|
||||
removeAllContent();
|
||||
|
||||
int columns = calculateColumnCount();
|
||||
if (0 == columns)
|
||||
return;
|
||||
|
||||
int columnWidth = parentWidget()->width() / columns;
|
||||
|
||||
//qDebug() << "parentWidth:" << parentWidget()->width() << "columnWidth:" << columnWidth << "columns:" << columns;
|
||||
|
||||
setColumnCount(columns);
|
||||
for (int i = 0; i < columns; i++)
|
||||
setColumnWidth(i, columnWidth);
|
||||
|
||||
decltype(m_document->materialIdList.size()) materialIndex = 0;
|
||||
while (materialIndex < m_document->materialIdList.size()) {
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem(this);
|
||||
item->setFlags((item->flags() | Qt::ItemIsEnabled) & ~(Qt::ItemIsSelectable) & ~(Qt::ItemIsEditable));
|
||||
for (int col = 0; col < columns && materialIndex < m_document->materialIdList.size(); col++, materialIndex++) {
|
||||
const auto &materialId = m_document->materialIdList[materialIndex];
|
||||
item->setSizeHint(col, QSize(columnWidth, MaterialWidget::preferredHeight() + 2));
|
||||
item->setData(col, Qt::UserRole, materialId.toString());
|
||||
MaterialWidget *widget = new MaterialWidget(m_document, materialId);
|
||||
connect(widget, &MaterialWidget::modifyMaterial, this, &MaterialListWidget::modifyMaterial);
|
||||
connect(widget, &MaterialWidget::cornerButtonClicked, this, &MaterialListWidget::cornerButtonClicked);
|
||||
widget->previewWidget()->setGraphicsFunctions(this);
|
||||
setItemWidget(item, col, widget);
|
||||
widget->reload();
|
||||
widget->updateCheckedState(isMaterialSelected(materialId));
|
||||
m_itemMap[materialId] = std::make_pair(item, col);
|
||||
}
|
||||
invisibleRootItem()->addChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialListWidget::setCornerButtonVisible(bool visible)
|
||||
{
|
||||
m_cornerButtonVisible = visible;
|
||||
}
|
||||
|
||||
void MaterialListWidget::setHasContextMenu(bool hasContextMenu)
|
||||
{
|
||||
m_hasContextMenu = hasContextMenu;
|
||||
}
|
||||
|
||||
void MaterialListWidget::removeAllContent()
|
||||
{
|
||||
m_itemMap.clear();
|
||||
clear();
|
||||
}
|
||||
|
||||
bool MaterialListWidget::mouseMove(QMouseEvent *event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MaterialListWidget::wheel(QWheelEvent *event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MaterialListWidget::mouseRelease(QMouseEvent *event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MaterialListWidget::mousePress(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::RightButton) {
|
||||
showContextMenu(mapFromGlobal(event->globalPos()));
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MaterialListWidget::mouseDoubleClick(QMouseEvent *event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MaterialListWidget::keyPress(QKeyEvent *event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void MaterialListWidget::copy()
|
||||
{
|
||||
if (m_selectedMaterialIds.empty() && m_currentSelectedMaterialId.isNull())
|
||||
return;
|
||||
|
||||
std::set<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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -31,6 +31,9 @@
|
|||
#include "rigwidget.h"
|
||||
#include "markiconcreator.h"
|
||||
#include "motionmanagewidget.h"
|
||||
#include "materialmanagewidget.h"
|
||||
#include "imageforever.h"
|
||||
#include "spinnableawesomebutton.h"
|
||||
|
||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
|
||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
|
||||
|
@ -150,6 +153,22 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
|
||||
QPushButton *rotateClockwiseButton = new QPushButton(QChar(fa::rotateright));
|
||||
Theme::initAwesomeButton(rotateClockwiseButton);
|
||||
|
||||
SpinnableAwesomeButton *regenerateButton = new SpinnableAwesomeButton();
|
||||
regenerateButton->setAwesomeIcon(QChar(fa::recycle));
|
||||
connect(m_document, &SkeletonDocument::meshGenerating, this, [=]() {
|
||||
regenerateButton->showSpinner(true);
|
||||
});
|
||||
connect(m_document, &SkeletonDocument::postProcessing, this, [=]() {
|
||||
regenerateButton->showSpinner(true);
|
||||
});
|
||||
connect(m_document, &SkeletonDocument::textureGenerating, this, [=]() {
|
||||
regenerateButton->showSpinner(true);
|
||||
});
|
||||
connect(m_document, &SkeletonDocument::resultTextureChanged, this, [=]() {
|
||||
regenerateButton->showSpinner(false);
|
||||
});
|
||||
connect(regenerateButton->button(), &QPushButton::clicked, m_document, &SkeletonDocument::regenerateMesh);
|
||||
|
||||
toolButtonLayout->addWidget(addButton);
|
||||
toolButtonLayout->addWidget(selectButton);
|
||||
|
@ -164,6 +183,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
toolButtonLayout->addSpacing(10);
|
||||
toolButtonLayout->addWidget(rotateCounterclockwiseButton);
|
||||
toolButtonLayout->addWidget(rotateClockwiseButton);
|
||||
toolButtonLayout->addSpacing(10);
|
||||
toolButtonLayout->addWidget(regenerateButton);
|
||||
|
||||
|
||||
QLabel *verticalLogoLabel = new QLabel;
|
||||
QImage verticalLogoImage;
|
||||
|
@ -216,6 +238,19 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
partTreeWidget->partPreviewChanged(part.first);
|
||||
});
|
||||
|
||||
QDockWidget *materialDocker = new QDockWidget(tr("Materials"), this);
|
||||
materialDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||
MaterialManageWidget *materialManageWidget = new MaterialManageWidget(m_document, materialDocker);
|
||||
materialDocker->setWidget(materialManageWidget);
|
||||
connect(materialManageWidget, &MaterialManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog);
|
||||
connect(materialManageWidget, &MaterialManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
|
||||
addDockWidget(Qt::RightDockWidgetArea, materialDocker);
|
||||
connect(materialDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) {
|
||||
Q_UNUSED(topLevel);
|
||||
for (const auto &material: m_document->materialMap)
|
||||
emit m_document->materialPreviewChanged(material.first);
|
||||
});
|
||||
|
||||
QDockWidget *rigDocker = new QDockWidget(tr("Rig"), this);
|
||||
rigDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||
m_rigWidget = new RigWidget(m_document, rigDocker);
|
||||
|
@ -247,7 +282,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
connect(motionManageWidget, &MotionManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
|
||||
addDockWidget(Qt::RightDockWidgetArea, motionDocker);
|
||||
|
||||
tabifyDockWidget(partTreeDocker, rigDocker);
|
||||
tabifyDockWidget(partTreeDocker, materialDocker);
|
||||
tabifyDockWidget(materialDocker, rigDocker);
|
||||
tabifyDockWidget(rigDocker, poseDocker);
|
||||
tabifyDockWidget(poseDocker, motionDocker);
|
||||
|
||||
|
@ -495,17 +531,17 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
});
|
||||
m_viewMenu->addAction(m_resetModelWidgetPosAction);
|
||||
|
||||
m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this);
|
||||
connect(m_toggleWireframeAction, &QAction::triggered, [=]() {
|
||||
m_modelRenderWidget->toggleWireframe();
|
||||
});
|
||||
m_viewMenu->addAction(m_toggleWireframeAction);
|
||||
//m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this);
|
||||
//connect(m_toggleWireframeAction, &QAction::triggered, [=]() {
|
||||
// m_modelRenderWidget->toggleWireframe();
|
||||
//});
|
||||
//m_viewMenu->addAction(m_toggleWireframeAction);
|
||||
|
||||
m_toggleSmoothNormalAction = new QAction(tr("Toggle Smooth Normal"), this);
|
||||
connect(m_toggleSmoothNormalAction, &QAction::triggered, [=]() {
|
||||
m_document->toggleSmoothNormal();
|
||||
});
|
||||
m_viewMenu->addAction(m_toggleSmoothNormalAction);
|
||||
//m_toggleSmoothNormalAction = new QAction(tr("Toggle Smooth Normal"), this);
|
||||
//connect(m_toggleSmoothNormalAction, &QAction::triggered, [=]() {
|
||||
// m_document->toggleSmoothNormal();
|
||||
//});
|
||||
//m_viewMenu->addAction(m_toggleSmoothNormalAction);
|
||||
|
||||
connect(m_viewMenu, &QMenu::aboutToShow, [=]() {
|
||||
m_resetModelWidgetPosAction->setEnabled(!isModelSitInVisibleArea(m_modelRenderWidget));
|
||||
|
@ -520,6 +556,13 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
});
|
||||
m_windowMenu->addAction(m_showPartsListAction);
|
||||
|
||||
m_showMaterialsAction = new QAction(tr("Materials"), this);
|
||||
connect(m_showMaterialsAction, &QAction::triggered, [=]() {
|
||||
materialDocker->show();
|
||||
materialDocker->raise();
|
||||
});
|
||||
m_windowMenu->addAction(m_showMaterialsAction);
|
||||
|
||||
m_showRigAction = new QAction(tr("Rig"), this);
|
||||
connect(m_showRigAction, &QAction::triggered, [=]() {
|
||||
rigDocker->show();
|
||||
|
@ -760,30 +803,30 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
connect(m_document, &SkeletonDocument::partRoundStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoundStateChanged);
|
||||
connect(m_document, &SkeletonDocument::partWrapStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partWrapStateChanged);
|
||||
connect(m_document, &SkeletonDocument::partColorStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partColorStateChanged);
|
||||
connect(m_document, &SkeletonDocument::partMetalnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partMetalnessChanged);
|
||||
connect(m_document, &SkeletonDocument::partRoughnessChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoughnessChanged);
|
||||
connect(m_document, &SkeletonDocument::partMaterialIdChanged, partTreeWidget, &SkeletonPartTreeWidget::partMaterialIdChanged);
|
||||
connect(m_document, &SkeletonDocument::partRemoved, partTreeWidget, &SkeletonPartTreeWidget::partRemoved);
|
||||
connect(m_document, &SkeletonDocument::cleanup, partTreeWidget, &SkeletonPartTreeWidget::removeAllContent);
|
||||
connect(m_document, &SkeletonDocument::partChecked, partTreeWidget, &SkeletonPartTreeWidget::partChecked);
|
||||
connect(m_document, &SkeletonDocument::partUnchecked, partTreeWidget, &SkeletonPartTreeWidget::partUnchecked);
|
||||
|
||||
connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
|
||||
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
|
||||
if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) {
|
||||
m_document->postProcess();
|
||||
}
|
||||
});
|
||||
//connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
|
||||
// if ((m_exportPreviewWidget && m_exportPreviewWidget->isVisible())) {
|
||||
// m_document->postProcess();
|
||||
// }
|
||||
//});
|
||||
connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::postProcess);
|
||||
connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::generateRig);
|
||||
connect(m_document, &SkeletonDocument::rigChanged, m_document, &SkeletonDocument::generateRig);
|
||||
connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateTexture);
|
||||
connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture);
|
||||
|
||||
//connect(m_document, &SkeletonDocument::resultTextureChanged, m_document, &SkeletonDocument::bakeAmbientOcclusionTexture);
|
||||
connect(m_document, &SkeletonDocument::resultTextureChanged, [=]() {
|
||||
m_modelRenderWidget->updateMesh(m_document->takeResultTextureMesh());
|
||||
});
|
||||
|
||||
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
|
||||
m_modelRenderWidget->updateMesh(m_document->takeResultMesh());
|
||||
});
|
||||
//connect(m_document, &SkeletonDocument::resultSkeletonChanged, [=]() {
|
||||
// m_skeletonRenderWidget->updateMesh(m_document->takeResultSkeletonMesh());
|
||||
//});
|
||||
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() {
|
||||
m_modelRenderWidget->setCursor(graphicsWidget->cursor());
|
||||
|
@ -821,6 +864,15 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
m_document->generatePosePreviews();
|
||||
});
|
||||
connect(m_document, &SkeletonDocument::resultRigChanged, m_document, &SkeletonDocument::generatePosePreviews);
|
||||
|
||||
connect(m_document, &SkeletonDocument::materialAdded, this, [=](QUuid materialId) {
|
||||
Q_UNUSED(materialId);
|
||||
m_document->generateMaterialPreviews();
|
||||
});
|
||||
connect(m_document, &SkeletonDocument::materialLayersChanged, this, [=](QUuid materialId) {
|
||||
Q_UNUSED(materialId);
|
||||
m_document->generateMaterialPreviews();
|
||||
});
|
||||
|
||||
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
|
||||
|
||||
|
@ -1037,6 +1089,26 @@ void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
|
|||
if (imageByteArray.size() > 0)
|
||||
ds3Writer.add("canvas.png", "asset", &imageByteArray);
|
||||
}
|
||||
|
||||
for (auto &material: snapshot.materials) {
|
||||
for (auto &layer: material.second) {
|
||||
for (auto &mapItem: layer.second) {
|
||||
auto findImageIdString = mapItem.find("linkData");
|
||||
if (findImageIdString == mapItem.end())
|
||||
continue;
|
||||
QUuid imageId = QUuid(findImageIdString->second);
|
||||
const QImage *image = ImageForever::get(imageId);
|
||||
if (nullptr == image)
|
||||
continue;
|
||||
QByteArray imageByteArray;
|
||||
QBuffer pngBuffer(&imageByteArray);
|
||||
pngBuffer.open(QIODevice::WriteOnly);
|
||||
image->save(&pngBuffer, "PNG");
|
||||
if (imageByteArray.size() > 0)
|
||||
ds3Writer.add("images/" + imageId.toString() + ".png", "asset", &imageByteArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ds3Writer.save(filename)) {
|
||||
setCurrentFilename(filename);
|
||||
|
@ -1063,6 +1135,24 @@ void SkeletonDocumentWindow::open()
|
|||
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
Ds3FileReader ds3Reader(filename);
|
||||
|
||||
for (int i = 0; i < ds3Reader.items().size(); ++i) {
|
||||
Ds3ReaderItem item = ds3Reader.items().at(i);
|
||||
if (item.type == "asset") {
|
||||
if (item.name.startsWith("images/")) {
|
||||
QString filename = item.name.split("/")[1];
|
||||
QString imageIdString = filename.split(".")[0];
|
||||
QUuid imageId = QUuid(imageIdString);
|
||||
if (!imageId.isNull()) {
|
||||
QByteArray data;
|
||||
ds3Reader.loadItem(item.name, &data);
|
||||
QImage image = QImage::fromData(data, "PNG");
|
||||
(void)ImageForever::add(&image, imageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ds3Reader.items().size(); ++i) {
|
||||
Ds3ReaderItem item = ds3Reader.items().at(i);
|
||||
if (item.type == "model") {
|
||||
|
@ -1104,19 +1194,11 @@ void SkeletonDocumentWindow::exportObjResult()
|
|||
return;
|
||||
}
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
m_modelRenderWidget->exportMeshAsObj(filename);
|
||||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::exportObjPlusMaterialsResult()
|
||||
{
|
||||
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
|
||||
tr("Wavefront (*.obj)"));
|
||||
if (filename.isEmpty()) {
|
||||
return;
|
||||
MeshLoader *resultMesh = m_document->takeResultMesh();
|
||||
if (nullptr != resultMesh) {
|
||||
resultMesh->exportAsObj(filename);
|
||||
delete resultMesh;
|
||||
}
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
m_modelRenderWidget->exportMeshAsObjPlusMaterials(filename);
|
||||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
};
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -93,6 +173,26 @@ void TextureGenerator::process()
|
|||
textureBorderPainter.begin(m_resultTextureBorderImage);
|
||||
textureBorderPainter.setRenderHint(QPainter::Antialiasing);
|
||||
textureBorderPainter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
QPainter textureNormalPainter;
|
||||
textureNormalPainter.begin(m_resultTextureNormalImage);
|
||||
textureNormalPainter.setRenderHint(QPainter::Antialiasing);
|
||||
textureNormalPainter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
QPainter textureMetalnessPainter;
|
||||
textureMetalnessPainter.begin(m_resultTextureMetalnessImage);
|
||||
textureMetalnessPainter.setRenderHint(QPainter::Antialiasing);
|
||||
textureMetalnessPainter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
QPainter textureRoughnessPainter;
|
||||
textureRoughnessPainter.begin(m_resultTextureRoughnessImage);
|
||||
textureRoughnessPainter.setRenderHint(QPainter::Antialiasing);
|
||||
textureRoughnessPainter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
QPainter textureAmbientOcclusionPainter;
|
||||
textureAmbientOcclusionPainter.begin(m_resultTextureAmbientOcclusionImage);
|
||||
textureAmbientOcclusionPainter.setRenderHint(QPainter::Antialiasing);
|
||||
textureAmbientOcclusionPainter.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
// round 1, paint background
|
||||
for (auto i = 0u; i < triangleUvs.size(); i++) {
|
||||
|
@ -100,11 +200,12 @@ void TextureGenerator::process()
|
|||
const ResultTriangleUv *uv = &triangleUvs[i];
|
||||
for (auto j = 0; j < 3; j++) {
|
||||
if (0 == j) {
|
||||
path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight);
|
||||
path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureSize, uv->uv[j][1] * TextureGenerator::m_textureSize);
|
||||
} else {
|
||||
path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight);
|
||||
path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureSize, uv->uv[j][1] * TextureGenerator::m_textureSize);
|
||||
}
|
||||
}
|
||||
path = expandedPainterPath(path);
|
||||
QPen textureBorderPen(triangleMaterials[i].color);
|
||||
textureBorderPen.setWidth(32);
|
||||
texturePainter.setPen(textureBorderPen);
|
||||
|
@ -116,14 +217,82 @@ void TextureGenerator::process()
|
|||
for (auto i = 0u; i < triangleUvs.size(); i++) {
|
||||
QPainterPath path;
|
||||
const ResultTriangleUv *uv = &triangleUvs[i];
|
||||
for (auto j = 0; j < 3; j++) {
|
||||
if (0 == j) {
|
||||
path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight);
|
||||
} else {
|
||||
path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight);
|
||||
}
|
||||
}
|
||||
float points[][2] = {
|
||||
{uv->uv[0][0] * TextureGenerator::m_textureSize, uv->uv[0][1] * TextureGenerator::m_textureSize},
|
||||
{uv->uv[1][0] * TextureGenerator::m_textureSize, uv->uv[1][1] * TextureGenerator::m_textureSize},
|
||||
{uv->uv[2][0] * TextureGenerator::m_textureSize, uv->uv[2][1] * TextureGenerator::m_textureSize}
|
||||
};
|
||||
path.moveTo(points[0][0], points[0][1]);
|
||||
path.lineTo(points[1][0], points[1][1]);
|
||||
path.lineTo(points[2][0], points[2][1]);
|
||||
path = expandedPainterPath(path);
|
||||
// Fill base color
|
||||
texturePainter.fillPath(path, QBrush(triangleMaterials[i].color));
|
||||
// Copy color texture if there is one
|
||||
const std::pair<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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#include "texturetype.h"
|
||||
|
||||
IMPL_TextureTypeToDispName
|
||||
IMPL_TextureTypeToString
|
||||
IMPL_TextureTypeFromString
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue