Add mesh seam combine algorithm

Not enable by default, should enable the weldEnabled in the SkeletonDocument class.
master
Jeremy Hu 2018-09-22 18:31:02 +08:00
parent a419a0fb7a
commit 6ffbdc9c7b
13 changed files with 308 additions and 44 deletions

View File

@ -194,6 +194,12 @@ HEADERS += src/posepreviewsgenerator.h
SOURCES += src/posewidget.cpp
HEADERS += src/posewidget.h
SOURCES += src/meshweldseam.cpp
HEADERS += src/meshweldseam.h
SOURCES += src/advancesettingwidget.cpp
HEADERS += src/advancesettingwidget.h
SOURCES += src/main.cpp
HEADERS += src/version.h

View File

@ -0,0 +1,22 @@
#include <QFormLayout>
#include <QCheckBox>
#include "advancesettingwidget.h"
#include "dust3dutil.h"
AdvanceSettingWidget::AdvanceSettingWidget(const SkeletonDocument *document, QWidget *parent) :
QDialog(parent),
m_document(document)
{
QCheckBox *enableWeldBox = new QCheckBox();
enableWeldBox->setChecked(document->weldEnabled);
connect(enableWeldBox, &QCheckBox::stateChanged, this, [=]() {
emit enableWeld(enableWeldBox->isChecked());
});
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Weld"), enableWeldBox);
setLayout(formLayout);
connect(this, &AdvanceSettingWidget::enableWeld, document, &SkeletonDocument::enableWeld);
}

View File

@ -0,0 +1,17 @@
#ifndef ADVANCE_SETTING_WIDGET_H
#define ADVANCE_SETTING_WIDGET_H
#include <QDialog>
#include "skeletondocument.h"
class AdvanceSettingWidget : public QDialog
{
Q_OBJECT
signals:
void enableWeld(bool enabled);
public:
AdvanceSettingWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
private:
const SkeletonDocument *m_document = nullptr;
};
#endif

View File

@ -120,6 +120,9 @@ void ExportPreviewWidget::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
checkSpinner();
if (m_document->isPostProcessResultObsolete()) {
m_document->postProcess();
}
}
void ExportPreviewWidget::checkSpinner()

View File

@ -1,6 +1,7 @@
#include <vector>
#include <QGuiApplication>
#include <QElapsedTimer>
#include <unordered_set>
#include "meshgenerator.h"
#include "dust3dutil.h"
#include "skeletondocument.h"
@ -9,6 +10,7 @@
#include "theme.h"
#include "positionmap.h"
#include "meshquadify.h"
#include "meshweldseam.h"
bool MeshGenerator::m_enableDebug = false;
PositionMap<int> *MeshGenerator::m_forMakePositionKey = new PositionMap<int>;
@ -31,12 +33,12 @@ void GeneratedCacheContext::updateComponentCombinableMesh(QString componentId, v
MeshGenerator::MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread) :
m_snapshot(snapshot),
m_mesh(nullptr),
//m_preview(nullptr),
m_thread(thread),
m_meshResultContext(nullptr),
m_sharedContextWidget(nullptr),
m_cacheContext(nullptr),
m_smoothNormal(true)
m_smoothNormal(true),
m_weldEnabled(true)
{
}
@ -55,6 +57,11 @@ void MeshGenerator::setSmoothNormal(bool smoothNormal)
m_smoothNormal = smoothNormal;
}
void MeshGenerator::setWeldEnabled(bool weldEnabled)
{
m_weldEnabled = weldEnabled;
}
void MeshGenerator::setGeneratedCacheContext(GeneratedCacheContext *cacheContext)
{
m_cacheContext = cacheContext;
@ -474,10 +481,8 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
continue;
bool childInverse = false;
void *childCombinedMesh = combineComponentMesh(childId, &childInverse);
if (smoothSeam) {
for (const auto &positionIt: m_cacheContext->componentPositions[childId]) {
positionsBeforeCombination.addPosition(positionIt.x(), positionIt.y(), positionIt.z(), true);
}
for (const auto &positionIt: m_cacheContext->componentPositions[childId]) {
positionsBeforeCombination.addPosition(positionIt.x(), positionIt.y(), positionIt.z(), true);
}
for (const auto &verticesSourceIt: m_cacheContext->componentVerticesSources[childId].map()) {
verticesSources.map()[verticesSourceIt.first] = verticesSourceIt.second;
@ -502,33 +507,55 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
}
if (nullptr != resultMesh) {
if (smoothSeam || smoothAll) {
int meshIdForSmooth = convertFromCombinableMesh(m_meshliteContext, resultMesh);
std::vector<QVector3D> positionsBeforeSmooth;
loadMeshVerticesPositions(m_meshliteContext, meshIdForSmooth, positionsBeforeSmooth);
int meshIdForSmooth = convertFromCombinableMesh(m_meshliteContext, resultMesh);
std::vector<QVector3D> positionsBeforeSmooth;
loadMeshVerticesPositions(m_meshliteContext, meshIdForSmooth, positionsBeforeSmooth);
if (!positionsBeforeSmooth.empty()) {
if (!positionsBeforeSmooth.empty()) {
std::vector<int> seamVerticesIds;
std::unordered_set<int> seamVerticesIndicies;
if (smoothSeam) {
int *seamVerticesIndicies = new int[positionsBeforeSmooth.size()];
int seamVerticesNum = 0;
for (size_t vertexIndex = 0; vertexIndex < positionsBeforeSmooth.size(); vertexIndex++) {
const auto &oldPosition = positionsBeforeSmooth[vertexIndex];
if (!positionsBeforeCombination.findPosition(oldPosition.x(), oldPosition.y(), oldPosition.z())) {
seamVerticesIndicies[seamVerticesNum++] = vertexIndex + 1;
if (!positionsBeforeCombination.map().empty()) {
for (size_t vertexIndex = 0; vertexIndex < positionsBeforeSmooth.size(); vertexIndex++) {
const auto &oldPosition = positionsBeforeSmooth[vertexIndex];
if (!positionsBeforeCombination.findPosition(oldPosition.x(), oldPosition.y(), oldPosition.z())) {
seamVerticesIds.push_back(vertexIndex + 1);
seamVerticesIndicies.insert(vertexIndex);
}
}
}
bool meshChanged = false;
if (m_weldEnabled) {
if (!seamVerticesIndicies.empty()) {
int weldedMeshId = meshWeldSeam(m_meshliteContext, meshIdForSmooth, 0.025, seamVerticesIndicies);
{
void *testCombinableMesh = convertToCombinableMesh(m_meshliteContext, weldedMeshId);
if (nullptr != testCombinableMesh) {
deleteCombinableMesh(testCombinableMesh);
meshIdForSmooth = weldedMeshId;
meshChanged = true;
} else {
qDebug() << "Weld seam failed, fall back";
}
}
if (seamVerticesNum > 0) {
//qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum;
meshlite_smooth_vertices(m_meshliteContext, meshIdForSmooth, smoothSeamFactor, seamVerticesIndicies, seamVerticesNum);
}
delete[] seamVerticesIndicies;
}
}
if (smoothAll) {
meshlite_smooth(m_meshliteContext, meshIdForSmooth, smoothAllFactor);
if (smoothSeam) {
if (!seamVerticesIds.empty()) {
//qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum;
meshlite_smooth_vertices(m_meshliteContext, meshIdForSmooth, smoothSeamFactor, seamVerticesIds.data(), seamVerticesIds.size());
meshChanged = true;
}
}
if (smoothAll) {
meshlite_smooth(m_meshliteContext, meshIdForSmooth, smoothAllFactor);
meshChanged = true;
}
if (meshChanged) {
std::vector<QVector3D> positionsAfterSmooth;
loadMeshVerticesPositions(m_meshliteContext, meshIdForSmooth, positionsAfterSmooth);
Q_ASSERT(positionsBeforeSmooth.size() == positionsAfterSmooth.size());
@ -543,7 +570,6 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
verticesSources.addPosition(smoothedPosition.x(), smoothedPosition.y(), smoothedPosition.z(), source);
}
}
deleteCombinableMesh(resultMesh);
resultMesh = convertToCombinableMesh(m_meshliteContext, meshIdForSmooth);
}
@ -655,12 +681,6 @@ void MeshGenerator::process()
bmeshNodes.second.begin(), bmeshNodes.second.end());
}
//if (resultMeshId > 0) {
// resultMeshId = meshlite_combine_coplanar_faces(m_meshliteContext, resultMeshId);
// if (resultMeshId > 0)
// resultMeshId = meshlite_fix_hole(m_meshliteContext, resultMeshId);
//}
int triangulatedFinalMeshId = resultMeshId;
if (triangulatedFinalMeshId > 0) {
std::set<std::pair<PositionMapKey, PositionMapKey>> sharedQuadEdges;
@ -676,8 +696,6 @@ void MeshGenerator::process()
}
if (resultMeshId > 0) {
//int triangulatedFinalMeshId = meshlite_triangulate(m_meshliteContext, resultMeshId);
//triangulatedFinalMeshId = resultMeshId;
loadGeneratedPositionsToMeshResultContext(m_meshliteContext, triangulatedFinalMeshId);
m_mesh = new MeshLoader(m_meshliteContext, resultMeshId, triangulatedFinalMeshId, Theme::white, &m_meshResultContext->triangleColors(), m_smoothNormal);
}

View File

@ -36,6 +36,7 @@ public:
void addPartPreviewRequirement(const QUuid &partId);
void setGeneratedCacheContext(GeneratedCacheContext *cacheContext);
void setSmoothNormal(bool smoothNormal);
void setWeldEnabled(bool weldEnabled);
MeshLoader *takeResultMesh();
MeshLoader *takePartPreviewMesh(const QUuid &partId);
const std::set<QUuid> &requirePreviewPartIds();
@ -57,6 +58,7 @@ private:
void *m_meshliteContext;
GeneratedCacheContext *m_cacheContext;
bool m_smoothNormal;
bool m_weldEnabled;
float m_mainProfileMiddleX;
float m_sideProfileMiddleX;
float m_mainProfileMiddleY;

View File

@ -1,5 +1,6 @@
#include <QDebug>
#include <unordered_set>
#include <vector>
#include "meshquadify.h"
#include "meshlite.h"
@ -11,12 +12,12 @@ int meshQuadify(void *meshlite, int meshId, const std::set<std::pair<PositionMap
int vertexArrayLen = meshlite_get_vertex_position_array(meshlite, meshId, vertexPositions, vertexCount * 3);
int offset = 0;
Q_ASSERT(vertexArrayLen == vertexCount * 3);
std::map<int, PositionMapKey> positionKeyMap;
std::vector<PositionMapKey> positionKeyMap;
for (int i = 0; i < vertexCount; i++) {
float x = vertexPositions[offset + 0];
float y = vertexPositions[offset + 1];
float z = vertexPositions[offset + 2];
positionKeyMap[i] = positionMapForMakeKey->makeKey(x, y, z);
positionKeyMap.push_back(positionMapForMakeKey->makeKey(x, y, z));
offset += 3;
}
int faceCount = meshlite_get_face_count(meshlite, meshId);

159
src/meshweldseam.cpp Normal file
View File

@ -0,0 +1,159 @@
#include <QtGlobal>
#include <QDebug>
#include <map>
#include <vector>
#include <QVector3D>
#include <unordered_map>
#include "meshweldseam.h"
#include "meshutil.h"
int meshWeldSeam(void *meshlite, int meshId, float allowedSmallestDistance, const std::unordered_set<int> &seamVerticesIndicies)
{
int vertexCount = meshlite_get_vertex_count(meshlite, meshId);
float *vertexPositions = new float[vertexCount * 3];
int vertexArrayLen = meshlite_get_vertex_position_array(meshlite, meshId, vertexPositions, vertexCount * 3);
int offset = 0;
Q_ASSERT(vertexArrayLen == vertexCount * 3);
std::vector<QVector3D> positions;
for (int i = 0; i < vertexCount; i++) {
float x = vertexPositions[offset + 0];
float y = vertexPositions[offset + 1];
float z = vertexPositions[offset + 2];
positions.push_back(QVector3D(x, y, z));
offset += 3;
}
int faceCount = meshlite_get_face_count(meshlite, meshId);
int *faceVertexNumAndIndices = new int[faceCount * MAX_VERTICES_PER_FACE];
int filledLength = meshlite_get_face_index_array(meshlite, meshId, faceVertexNumAndIndices, faceCount * MAX_VERTICES_PER_FACE);
int i = 0;
std::vector<std::vector<int>> newFaceIndicies;
while (i < filledLength) {
int num = faceVertexNumAndIndices[i++];
Q_ASSERT(num > 0 && num <= MAX_VERTICES_PER_FACE);
if (num < 3) {
i += num;
continue;
}
std::vector<int> indices;
for (int j = 0; j < num; j++) {
int index = faceVertexNumAndIndices[i++];
Q_ASSERT(index >= 0 && index < vertexCount);
indices.push_back(index);
}
newFaceIndicies.push_back(indices);
}
float squareOfAllowedSmallestDistance = allowedSmallestDistance * allowedSmallestDistance;
int weldedMesh = 0;
std::map<int, int> weldVertexToMap;
std::unordered_set<int> weldTargetVertices;
std::unordered_set<int> processedFaces;
std::map<std::pair<int, int>, std::pair<int, int>> triangleEdgeMap;
std::unordered_map<int, int> vertexAdjFaceCountMap;
for (int i = 0; i < (int)newFaceIndicies.size(); i++) {
const auto &faceIndicies = newFaceIndicies[i];
if (faceIndicies.size() == 3) {
vertexAdjFaceCountMap[faceIndicies[0]]++;
vertexAdjFaceCountMap[faceIndicies[1]]++;
vertexAdjFaceCountMap[faceIndicies[2]]++;
triangleEdgeMap[std::make_pair(faceIndicies[0], faceIndicies[1])] = std::make_pair(i, faceIndicies[2]);
triangleEdgeMap[std::make_pair(faceIndicies[1], faceIndicies[2])] = std::make_pair(i, faceIndicies[0]);
triangleEdgeMap[std::make_pair(faceIndicies[2], faceIndicies[0])] = std::make_pair(i, faceIndicies[1]);
}
}
for (int i = 0; i < (int)newFaceIndicies.size(); i++) {
if (processedFaces.find(i) != processedFaces.end())
continue;
const auto &faceIndicies = newFaceIndicies[i];
if (faceIndicies.size() == 3) {
bool indiciesSeamCheck[3] = {
seamVerticesIndicies.empty() || seamVerticesIndicies.find(faceIndicies[0]) != seamVerticesIndicies.end(),
seamVerticesIndicies.empty() || seamVerticesIndicies.find(faceIndicies[1]) != seamVerticesIndicies.end(),
seamVerticesIndicies.empty() || seamVerticesIndicies.find(faceIndicies[2]) != seamVerticesIndicies.end()
};
for (int j = 0; j < 3; j++) {
int next = (j + 1) % 3;
int nextNext = (j + 2) % 3;
if (indiciesSeamCheck[j] && indiciesSeamCheck[next]) {
std::pair<int, int> edge = std::make_pair(faceIndicies[j], faceIndicies[next]);
int thirdVertexIndex = faceIndicies[nextNext];
if ((positions[edge.first] - positions[edge.second]).lengthSquared() < squareOfAllowedSmallestDistance) {
auto oppositeEdge = std::make_pair(edge.second, edge.first);
auto findOppositeFace = triangleEdgeMap.find(oppositeEdge);
if (findOppositeFace == triangleEdgeMap.end()) {
qDebug() << "Find opposite edge failed";
continue;
}
int oppositeFaceIndex = findOppositeFace->second.first;
// Weld on the longer edge vertex
if (((positions[edge.first] - positions[thirdVertexIndex]).lengthSquared() <
(positions[edge.second] - positions[thirdVertexIndex]).lengthSquared()) &&
vertexAdjFaceCountMap[edge.second] <= 4 &&
weldVertexToMap.find(edge.second) == weldVertexToMap.end()) {
weldVertexToMap[edge.second] = edge.first;
weldTargetVertices.insert(edge.first);
processedFaces.insert(i);
processedFaces.insert(oppositeFaceIndex);
break;
} else if (vertexAdjFaceCountMap[edge.first] <= 4 &&
weldVertexToMap.find(edge.first) == weldVertexToMap.end()) {
weldVertexToMap[edge.first] = edge.second;
weldTargetVertices.insert(edge.second);
processedFaces.insert(i);
processedFaces.insert(oppositeFaceIndex);
break;
}
}
}
}
}
}
std::vector<int> newFaceVertexNumAndIndices;
int weldedCount = 0;
int faceCountAfterWeld = 0;
for (int i = 0; i < (int)newFaceIndicies.size(); i++) {
const auto &faceIndicies = newFaceIndicies[i];
std::vector<int> mappedFaceIndicies;
bool errored = false;
for (const auto &index: faceIndicies) {
int finalIndex = index;
int mapTimes = 0;
while (mapTimes < 500) {
auto findMapResult = weldVertexToMap.find(finalIndex);
if (findMapResult == weldVertexToMap.end())
break;
finalIndex = findMapResult->second;
mapTimes++;
}
if (mapTimes >= 500) {
qDebug() << "Map too much times";
errored = true;
break;
}
mappedFaceIndicies.push_back(finalIndex);
}
if (errored || mappedFaceIndicies.size() < 3)
continue;
bool welded = false;
for (decltype(mappedFaceIndicies.size()) j = 0; j < mappedFaceIndicies.size(); j++) {
int next = (j + 1) % 3;
if (mappedFaceIndicies[j] == mappedFaceIndicies[next]) {
welded = true;
break;
}
}
if (welded) {
weldedCount++;
continue;
}
faceCountAfterWeld++;
newFaceVertexNumAndIndices.push_back(mappedFaceIndicies.size());
for (const auto &index: mappedFaceIndicies) {
newFaceVertexNumAndIndices.push_back(index);
}
}
qDebug() << "Welded" << weldedCount << "triangles(" << newFaceIndicies.size() << " - " << weldedCount << " = " << faceCountAfterWeld << ")";
weldedMesh = meshlite_build(meshlite, vertexPositions, vertexCount, newFaceVertexNumAndIndices.data(), newFaceVertexNumAndIndices.size());
delete[] faceVertexNumAndIndices;
delete[] vertexPositions;
return weldedMesh;
}

9
src/meshweldseam.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef MESH_WELD_SEAM_H
#define MESH_WELD_SEAM_H
#include "meshlite.h"
#include <unordered_set>
int meshWeldSeam(void *meshlite, int meshId, float allowedSmallestDistance,
const std::unordered_set<int> &seamVerticesIndicies=std::unordered_set<int>());
#endif

View File

@ -28,6 +28,7 @@ SkeletonDocument::SkeletonDocument() :
textureAmbientOcclusionImage(nullptr),
textureColorImage(nullptr),
rigType(RigType::None),
weldEnabled(false),
// private
m_isResultMeshObsolete(false),
m_meshGenerator(nullptr),
@ -1213,6 +1214,12 @@ void SkeletonDocument::toggleSmoothNormal()
regenerateMesh();
}
void SkeletonDocument::enableWeld(bool enabled)
{
weldEnabled = enabled;
regenerateMesh();
}
void SkeletonDocument::generateMesh()
{
if (nullptr != m_meshGenerator || m_batchChangeRefCount > 0) {
@ -1233,6 +1240,7 @@ void SkeletonDocument::generateMesh()
resetDirtyFlags();
m_meshGenerator = new MeshGenerator(snapshot, thread);
m_meshGenerator->setSmoothNormal(m_smoothNormal);
m_meshGenerator->setWeldEnabled(weldEnabled);
m_meshGenerator->setGeneratedCacheContext(&m_generatedCacheContext);
if (nullptr != m_sharedContextWidget)
m_meshGenerator->setSharedContextWidget(m_sharedContextWidget);

View File

@ -465,6 +465,7 @@ public: // need initialize
QImage *textureAmbientOcclusionImage;
QImage *textureColorImage;
RigType rigType;
bool weldEnabled;
public:
SkeletonDocument();
~SkeletonDocument();
@ -594,6 +595,7 @@ public slots:
void enableAllPositionRelatedLocks();
void disableAllPositionRelatedLocks();
void toggleSmoothNormal();
void enableWeld(bool enabled);
void setRigType(RigType toRigType);
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters);
void removePose(QUuid poseId);

View File

@ -97,7 +97,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_document(nullptr),
m_firstShow(true),
m_documentSaved(true),
m_exportPreviewWidget(nullptr)
m_exportPreviewWidget(nullptr),
m_advanceSettingWidget(nullptr)
{
if (!g_logBrowser) {
g_logBrowser = new LogBrowser;
@ -544,6 +545,12 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog);
m_windowMenu->addAction(m_showDebugDialogAction);
m_showAdvanceSettingAction = new QAction(tr("Advance"), this);
connect(m_showAdvanceSettingAction, &QAction::triggered, this, &SkeletonDocumentWindow::showAdvanceSetting);
#ifndef NDEBUG
m_windowMenu->addAction(m_showAdvanceSettingAction);
#endif
m_helpMenu = menuBar()->addMenu(tr("Help"));
m_viewSourceAction = new QAction(tr("Fork me on GitHub"), this);
@ -1061,6 +1068,15 @@ void SkeletonDocumentWindow::open()
setCurrentFilename(filename);
}
void SkeletonDocumentWindow::showAdvanceSetting()
{
if (nullptr == m_advanceSettingWidget) {
m_advanceSettingWidget = new AdvanceSettingWidget(m_document, this);
}
m_advanceSettingWidget->show();
m_advanceSettingWidget->raise();
}
void SkeletonDocumentWindow::exportObjResult()
{
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
@ -1097,10 +1113,8 @@ void SkeletonDocumentWindow::showExportPreview()
connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
registerDialog(m_exportPreviewWidget);
}
if (m_document->isPostProcessResultObsolete()) {
m_document->postProcess();
}
m_exportPreviewWidget->show();
m_exportPreviewWidget->raise();
}
void SkeletonDocumentWindow::exportGltfResult()

View File

@ -13,6 +13,7 @@
#include "rigwidget.h"
#include "skeletonbonemark.h"
#include "posemanagewidget.h"
#include "advancesettingwidget.h"
class SkeletonGraphicsWidget;
@ -59,6 +60,7 @@ public slots:
void updateRigWeightRenderWidget();
void registerDialog(QWidget *widget);
void unregisterDialog(QWidget *widget);
void showAdvanceSetting();
private:
void initLockButton(QPushButton *button);
void setCurrentFilename(const QString &filename);
@ -68,6 +70,7 @@ private:
bool m_firstShow;
bool m_documentSaved;
ExportPreviewWidget *m_exportPreviewWidget;
AdvanceSettingWidget *m_advanceSettingWidget;
std::vector<QWidget *> m_dialogs;
private:
QString m_currentFilename;
@ -75,7 +78,6 @@ private:
ModelWidget *m_modelRenderWidget;
SkeletonGraphicsWidget *m_graphicsWidget;
RigWidget *m_rigWidget;
PoseManageWidget *m_poseManageWidget;
QMenu *m_fileMenu;
QAction *m_newWindowAction;
@ -135,6 +137,7 @@ private:
QAction *m_showDebugDialogAction;
QAction *m_showRigAction;
QAction *m_showPosesAction;
QAction *m_showAdvanceSettingAction;
QMenu *m_helpMenu;
QAction *m_viewSourceAction;