2019-02-18 12:57:18 +00:00
|
|
|
#include <QDebug>
|
2018-08-27 08:50:40 +00:00
|
|
|
#include <QElapsedTimer>
|
2019-02-18 12:57:18 +00:00
|
|
|
#include <QVector2D>
|
|
|
|
#include <QGuiApplication>
|
2019-02-21 22:48:15 +00:00
|
|
|
#include <QMatrix4x4>
|
2019-02-18 12:57:18 +00:00
|
|
|
#include <nodemesh/builder.h>
|
|
|
|
#include <nodemesh/modifier.h>
|
2019-02-19 11:21:12 +00:00
|
|
|
#include <nodemesh/misc.h>
|
2019-02-18 12:57:18 +00:00
|
|
|
#include <nodemesh/recombiner.h>
|
2018-04-07 08:44:39 +00:00
|
|
|
#include "meshgenerator.h"
|
2018-10-25 00:19:38 +00:00
|
|
|
#include "util.h"
|
2018-10-26 23:04:45 +00:00
|
|
|
#include "trianglesourcenoderesolve.h"
|
2019-05-05 12:49:26 +00:00
|
|
|
#include "cutface.h"
|
2019-05-19 03:21:38 +00:00
|
|
|
#include "parttarget.h"
|
|
|
|
#include "theme.h"
|
2019-05-20 13:38:01 +00:00
|
|
|
#include "partbase.h"
|
2018-04-11 06:15:11 +00:00
|
|
|
|
2018-10-25 00:19:38 +00:00
|
|
|
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
|
2019-02-18 12:57:18 +00:00
|
|
|
m_snapshot(snapshot)
|
2018-04-07 08:44:39 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
MeshGenerator::~MeshGenerator()
|
|
|
|
{
|
2019-02-18 12:57:18 +00:00
|
|
|
for (auto &it: m_partPreviewMeshes)
|
|
|
|
delete it.second;
|
|
|
|
delete m_resultMesh;
|
2018-04-07 08:44:39 +00:00
|
|
|
delete m_snapshot;
|
2018-10-25 00:19:38 +00:00
|
|
|
delete m_outcome;
|
2019-07-02 21:49:18 +00:00
|
|
|
delete m_cutFaceTransforms;
|
2019-07-02 23:07:46 +00:00
|
|
|
delete m_nodesCutFaces;
|
2018-04-07 08:44:39 +00:00
|
|
|
}
|
|
|
|
|
2019-01-07 13:03:42 +00:00
|
|
|
bool MeshGenerator::isSucceed()
|
|
|
|
{
|
|
|
|
return m_isSucceed;
|
|
|
|
}
|
|
|
|
|
2018-05-07 17:16:58 +00:00
|
|
|
MeshLoader *MeshGenerator::takeResultMesh()
|
2018-04-07 08:44:39 +00:00
|
|
|
{
|
2019-02-18 12:57:18 +00:00
|
|
|
MeshLoader *resultMesh = m_resultMesh;
|
|
|
|
m_resultMesh = nullptr;
|
2018-04-07 08:44:39 +00:00
|
|
|
return resultMesh;
|
|
|
|
}
|
|
|
|
|
2018-09-18 06:22:29 +00:00
|
|
|
MeshLoader *MeshGenerator::takePartPreviewMesh(const QUuid &partId)
|
2018-04-07 08:44:39 +00:00
|
|
|
{
|
2019-02-18 12:57:18 +00:00
|
|
|
MeshLoader *resultMesh = m_partPreviewMeshes[partId];
|
|
|
|
m_partPreviewMeshes[partId] = nullptr;
|
2018-09-18 06:22:29 +00:00
|
|
|
return resultMesh;
|
2018-04-07 08:44:39 +00:00
|
|
|
}
|
|
|
|
|
2018-09-18 06:22:29 +00:00
|
|
|
const std::set<QUuid> &MeshGenerator::generatedPreviewPartIds()
|
|
|
|
{
|
|
|
|
return m_generatedPreviewPartIds;
|
2018-04-07 08:44:39 +00:00
|
|
|
}
|
|
|
|
|
2018-10-26 23:04:45 +00:00
|
|
|
Outcome *MeshGenerator::takeOutcome()
|
2018-04-26 02:23:22 +00:00
|
|
|
{
|
2018-10-25 00:19:38 +00:00
|
|
|
Outcome *outcome = m_outcome;
|
|
|
|
m_outcome = nullptr;
|
|
|
|
return outcome;
|
2018-04-26 02:23:22 +00:00
|
|
|
}
|
|
|
|
|
2019-07-02 21:49:18 +00:00
|
|
|
std::map<QUuid, nodemesh::Builder::CutFaceTransform> *MeshGenerator::takeCutFaceTransforms()
|
|
|
|
{
|
|
|
|
auto cutFaceTransforms = m_cutFaceTransforms;
|
|
|
|
m_cutFaceTransforms = nullptr;
|
|
|
|
return cutFaceTransforms;
|
|
|
|
}
|
|
|
|
|
2019-07-07 06:27:58 +00:00
|
|
|
std::map<QUuid, std::map<QString, QVector2D>> *MeshGenerator::takeNodesCutFaces()
|
2019-07-02 23:07:46 +00:00
|
|
|
{
|
|
|
|
auto nodesCutFaces = m_nodesCutFaces;
|
|
|
|
m_nodesCutFaces = nullptr;
|
|
|
|
return nodesCutFaces;
|
|
|
|
}
|
|
|
|
|
2018-08-27 08:50:40 +00:00
|
|
|
void MeshGenerator::collectParts()
|
2018-04-07 08:44:39 +00:00
|
|
|
{
|
2018-08-27 08:50:40 +00:00
|
|
|
for (const auto &node: m_snapshot->nodes) {
|
|
|
|
QString partId = valueOfKeyInMapOrEmpty(node.second, "partId");
|
|
|
|
if (partId.isEmpty())
|
|
|
|
continue;
|
|
|
|
m_partNodeIds[partId].insert(node.first);
|
|
|
|
}
|
|
|
|
for (const auto &edge: m_snapshot->edges) {
|
|
|
|
QString partId = valueOfKeyInMapOrEmpty(edge.second, "partId");
|
|
|
|
if (partId.isEmpty())
|
|
|
|
continue;
|
|
|
|
m_partEdgeIds[partId].insert(edge.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
bool MeshGenerator::checkIsPartDirty(const QString &partIdString)
|
2018-08-27 08:50:40 +00:00
|
|
|
{
|
2019-02-18 12:57:18 +00:00
|
|
|
auto findPart = m_snapshot->parts.find(partIdString);
|
2018-08-27 08:50:40 +00:00
|
|
|
if (findPart == m_snapshot->parts.end()) {
|
2019-02-18 12:57:18 +00:00
|
|
|
qDebug() << "Find part failed:" << partIdString;
|
2018-08-27 08:50:40 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return isTrueValueString(valueOfKeyInMapOrEmpty(findPart->second, "dirty"));
|
|
|
|
}
|
|
|
|
|
2019-05-19 03:21:38 +00:00
|
|
|
bool MeshGenerator::checkIsPartDependencyDirty(const QString &partIdString)
|
|
|
|
{
|
|
|
|
auto findPart = m_snapshot->parts.find(partIdString);
|
|
|
|
if (findPart == m_snapshot->parts.end()) {
|
|
|
|
qDebug() << "Find part failed:" << partIdString;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
QString cutFaceString = valueOfKeyInMapOrEmpty(findPart->second, "cutFace");
|
|
|
|
QUuid cutFaceLinkedPartId = QUuid(cutFaceString);
|
|
|
|
if (cutFaceLinkedPartId.isNull())
|
|
|
|
return false;
|
|
|
|
return checkIsPartDirty(cutFaceString);
|
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
bool MeshGenerator::checkIsComponentDirty(const QString &componentIdString)
|
|
|
|
{
|
|
|
|
bool isDirty = false;
|
|
|
|
|
|
|
|
const std::map<QString, QString> *component = &m_snapshot->rootComponent;
|
|
|
|
if (componentIdString != QUuid().toString()) {
|
|
|
|
auto findComponent = m_snapshot->components.find(componentIdString);
|
|
|
|
if (findComponent == m_snapshot->components.end()) {
|
|
|
|
qDebug() << "Component not found:" << componentIdString;
|
|
|
|
return isDirty;
|
|
|
|
}
|
|
|
|
component = &findComponent->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isTrueValueString(valueOfKeyInMapOrEmpty(*component, "dirty"))) {
|
|
|
|
isDirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString linkDataType = valueOfKeyInMapOrEmpty(*component, "linkDataType");
|
|
|
|
if ("partId" == linkDataType) {
|
|
|
|
QString partId = valueOfKeyInMapOrEmpty(*component, "linkData");
|
|
|
|
if (checkIsPartDirty(partId)) {
|
|
|
|
m_dirtyPartIds.insert(partId);
|
|
|
|
isDirty = true;
|
|
|
|
}
|
2019-05-19 03:21:38 +00:00
|
|
|
if (!isDirty) {
|
|
|
|
if (checkIsPartDependencyDirty(partId)) {
|
|
|
|
isDirty = true;
|
|
|
|
}
|
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto &childId: valueOfKeyInMapOrEmpty(*component, "children").split(",")) {
|
|
|
|
if (childId.isEmpty())
|
|
|
|
continue;
|
|
|
|
if (checkIsComponentDirty(childId)) {
|
|
|
|
isDirty = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isDirty)
|
|
|
|
m_dirtyComponentIds.insert(componentIdString);
|
|
|
|
|
|
|
|
return isDirty;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MeshGenerator::checkDirtyFlags()
|
|
|
|
{
|
|
|
|
checkIsComponentDirty(QUuid().toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString)
|
2018-08-27 08:50:40 +00:00
|
|
|
{
|
2019-02-18 12:57:18 +00:00
|
|
|
auto findPart = m_snapshot->parts.find(partIdString);
|
2018-08-27 08:50:40 +00:00
|
|
|
if (findPart == m_snapshot->parts.end()) {
|
2019-02-18 12:57:18 +00:00
|
|
|
qDebug() << "Find part failed:" << partIdString;
|
2018-08-27 08:50:40 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
2018-04-07 08:44:39 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
QUuid partId = QUuid(partIdString);
|
2018-08-27 08:50:40 +00:00
|
|
|
auto &part = findPart->second;
|
|
|
|
bool isDisabled = isTrueValueString(valueOfKeyInMapOrEmpty(part, "disabled"));
|
|
|
|
bool xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(part, "xMirrored"));
|
|
|
|
bool subdived = isTrueValueString(valueOfKeyInMapOrEmpty(part, "subdived"));
|
2019-02-18 12:57:18 +00:00
|
|
|
bool rounded = isTrueValueString(valueOfKeyInMapOrEmpty(part, "rounded"));
|
2019-03-09 22:10:39 +00:00
|
|
|
bool chamfered = isTrueValueString(valueOfKeyInMapOrEmpty(part, "chamfered"));
|
2018-08-27 08:50:40 +00:00
|
|
|
QString colorString = valueOfKeyInMapOrEmpty(part, "color");
|
2019-02-18 12:57:18 +00:00
|
|
|
QColor partColor = colorString.isEmpty() ? m_defaultPartColor : QColor(colorString);
|
|
|
|
float deformThickness = 1.0;
|
|
|
|
float deformWidth = 1.0;
|
2019-02-21 22:48:15 +00:00
|
|
|
float cutRotation = 0.0;
|
2019-05-19 03:21:38 +00:00
|
|
|
auto target = PartTargetFromString(valueOfKeyInMapOrEmpty(part, "target").toUtf8().constData());
|
2019-05-20 13:38:01 +00:00
|
|
|
auto base = PartBaseFromString(valueOfKeyInMapOrEmpty(part, "base").toUtf8().constData());
|
2019-02-21 22:48:15 +00:00
|
|
|
|
2019-07-07 06:27:58 +00:00
|
|
|
std::map<QString, QVector2D> cutTemplateMapByName;
|
2019-05-19 03:21:38 +00:00
|
|
|
std::vector<QVector2D> cutTemplate;
|
|
|
|
QString cutFaceString = valueOfKeyInMapOrEmpty(part, "cutFace");
|
|
|
|
QUuid cutFaceLinkedPartId = QUuid(cutFaceString);
|
|
|
|
if (!cutFaceLinkedPartId.isNull()) {
|
2019-07-07 06:27:58 +00:00
|
|
|
std::map<QString, std::tuple<float, float, float>> cutFaceNodeMap;
|
2019-05-19 03:21:38 +00:00
|
|
|
auto findCutFaceLinkedPart = m_snapshot->parts.find(cutFaceString);
|
|
|
|
if (findCutFaceLinkedPart == m_snapshot->parts.end()) {
|
|
|
|
qDebug() << "Find cut face linked part failed:" << cutFaceString;
|
|
|
|
} else {
|
|
|
|
// Build node info map
|
|
|
|
for (const auto &nodeIdString: m_partNodeIds[cutFaceString]) {
|
|
|
|
auto findNode = m_snapshot->nodes.find(nodeIdString);
|
|
|
|
if (findNode == m_snapshot->nodes.end()) {
|
|
|
|
qDebug() << "Find node failed:" << nodeIdString;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto &node = findNode->second;
|
|
|
|
float radius = valueOfKeyInMapOrEmpty(node, "radius").toFloat();
|
|
|
|
float x = (valueOfKeyInMapOrEmpty(node, "x").toFloat() - m_mainProfileMiddleX);
|
|
|
|
float y = (m_mainProfileMiddleY - valueOfKeyInMapOrEmpty(node, "y").toFloat());
|
2019-05-19 03:38:20 +00:00
|
|
|
cutFaceNodeMap.insert({nodeIdString, std::make_tuple(radius, x, y)});
|
2019-05-19 03:21:38 +00:00
|
|
|
}
|
|
|
|
// Build edge link
|
|
|
|
std::map<QString, std::vector<QString>> cutFaceNodeLinkMap;
|
|
|
|
for (const auto &edgeIdString: m_partEdgeIds[cutFaceString]) {
|
|
|
|
auto findEdge = m_snapshot->edges.find(edgeIdString);
|
|
|
|
if (findEdge == m_snapshot->edges.end()) {
|
|
|
|
qDebug() << "Find edge failed:" << edgeIdString;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto &edge = findEdge->second;
|
|
|
|
QString fromNodeIdString = valueOfKeyInMapOrEmpty(edge, "from");
|
|
|
|
QString toNodeIdString = valueOfKeyInMapOrEmpty(edge, "to");
|
|
|
|
cutFaceNodeLinkMap[fromNodeIdString].push_back(toNodeIdString);
|
|
|
|
cutFaceNodeLinkMap[toNodeIdString].push_back(fromNodeIdString);
|
|
|
|
}
|
|
|
|
// Find endpoint
|
|
|
|
QString endPointNodeIdString;
|
|
|
|
std::vector<std::pair<QString, std::tuple<float, float, float>>> endpointNodes;
|
|
|
|
for (const auto &it: cutFaceNodeLinkMap) {
|
|
|
|
if (1 == it.second.size()) {
|
|
|
|
const auto &findNode = cutFaceNodeMap.find(it.first);
|
|
|
|
if (findNode != cutFaceNodeMap.end())
|
|
|
|
endpointNodes.push_back({it.first, findNode->second});
|
|
|
|
}
|
|
|
|
}
|
2019-06-01 14:15:57 +00:00
|
|
|
bool isRing = endpointNodes.empty();
|
|
|
|
if (endpointNodes.empty()) {
|
|
|
|
for (const auto &it: cutFaceNodeMap) {
|
|
|
|
endpointNodes.push_back({it.first, it.second});
|
|
|
|
}
|
|
|
|
}
|
2019-05-19 03:21:38 +00:00
|
|
|
if (!endpointNodes.empty()) {
|
|
|
|
std::sort(endpointNodes.begin(), endpointNodes.end(), [](
|
|
|
|
const std::pair<QString, std::tuple<float, float, float>> &first,
|
|
|
|
const std::pair<QString, std::tuple<float, float, float>> &second) {
|
|
|
|
const auto &firstX = std::get<1>(first.second);
|
|
|
|
const auto &secondX = std::get<1>(second.second);
|
|
|
|
if (firstX < secondX) {
|
|
|
|
return true;
|
|
|
|
} else if (firstX > secondX) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
const auto &firstY = std::get<2>(first.second);
|
|
|
|
const auto &secondY = std::get<2>(second.second);
|
|
|
|
if (firstY > secondY) {
|
|
|
|
return true;
|
|
|
|
} else if (firstY < secondY) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
const auto &firstRadius = std::get<0>(first.second);
|
|
|
|
const auto &secondRadius = std::get<0>(second.second);
|
|
|
|
if (firstRadius < secondRadius) {
|
|
|
|
return true;
|
|
|
|
} else if (firstRadius > secondRadius) {
|
|
|
|
return false;
|
|
|
|
} else {
|
2019-06-29 09:42:45 +00:00
|
|
|
return false;
|
2019-05-19 03:21:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
endPointNodeIdString = endpointNodes[0].first;
|
|
|
|
}
|
|
|
|
// Loop all linked nodes
|
2019-07-07 06:27:58 +00:00
|
|
|
std::vector<std::tuple<float, float, float, QString>> cutFaceNodes;
|
2019-05-19 03:21:38 +00:00
|
|
|
std::set<QString> cutFaceVisitedNodeIds;
|
|
|
|
std::function<void (const QString &)> loopNodeLink;
|
|
|
|
loopNodeLink = [&](const QString &fromNodeIdString) {
|
|
|
|
auto findCutFaceNode = cutFaceNodeMap.find(fromNodeIdString);
|
|
|
|
if (findCutFaceNode == cutFaceNodeMap.end())
|
|
|
|
return;
|
|
|
|
if (cutFaceVisitedNodeIds.find(fromNodeIdString) != cutFaceVisitedNodeIds.end())
|
|
|
|
return;
|
|
|
|
cutFaceVisitedNodeIds.insert(fromNodeIdString);
|
2019-07-07 11:10:24 +00:00
|
|
|
cutFaceNodes.push_back(std::make_tuple(std::get<0>(findCutFaceNode->second),
|
2019-07-07 06:27:58 +00:00
|
|
|
std::get<1>(findCutFaceNode->second),
|
|
|
|
std::get<2>(findCutFaceNode->second),
|
2019-07-07 11:10:24 +00:00
|
|
|
fromNodeIdString));
|
2019-05-19 03:21:38 +00:00
|
|
|
auto findNeighbor = cutFaceNodeLinkMap.find(fromNodeIdString);
|
|
|
|
if (findNeighbor == cutFaceNodeLinkMap.end())
|
|
|
|
return;
|
|
|
|
for (const auto &it: findNeighbor->second) {
|
|
|
|
if (cutFaceVisitedNodeIds.find(it) == cutFaceVisitedNodeIds.end()) {
|
|
|
|
loopNodeLink(it);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if (!endPointNodeIdString.isEmpty()) {
|
|
|
|
loopNodeLink(endPointNodeIdString);
|
|
|
|
}
|
|
|
|
// Fetch points from linked nodes
|
2019-07-07 06:27:58 +00:00
|
|
|
std::vector<QString> cutTemplateNames;
|
|
|
|
cutFacePointsFromNodes(cutTemplate, cutFaceNodes, isRing, &cutTemplateNames);
|
|
|
|
for (size_t i = 0; i < cutTemplateNames.size(); ++i) {
|
|
|
|
cutTemplateMapByName.insert({cutTemplateNames[i], cutTemplate[i]});
|
|
|
|
}
|
2019-05-19 03:21:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cutTemplate.size() < 3) {
|
|
|
|
CutFace cutFace = CutFaceFromString(cutFaceString.toUtf8().constData());
|
|
|
|
cutTemplate = CutFaceToPoints(cutFace);
|
2019-07-07 06:27:58 +00:00
|
|
|
cutTemplateMapByName.clear();
|
|
|
|
for (size_t i = 0; i < cutTemplate.size(); ++i) {
|
|
|
|
cutTemplateMapByName.insert({cutFaceString + "/" + QString::number(i + 1), cutTemplate[i]});
|
|
|
|
}
|
2019-05-19 03:21:38 +00:00
|
|
|
}
|
2019-03-09 22:10:39 +00:00
|
|
|
if (chamfered)
|
|
|
|
nodemesh::chamferFace2D(&cutTemplate);
|
2019-05-19 03:21:38 +00:00
|
|
|
|
2019-02-21 22:48:15 +00:00
|
|
|
QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation");
|
|
|
|
if (!cutRotationString.isEmpty()) {
|
|
|
|
cutRotation = cutRotationString.toFloat();
|
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
|
2018-08-27 08:50:40 +00:00
|
|
|
QString thicknessString = valueOfKeyInMapOrEmpty(part, "deformThickness");
|
2019-02-18 12:57:18 +00:00
|
|
|
if (!thicknessString.isEmpty()) {
|
|
|
|
deformThickness = thicknessString.toFloat();
|
|
|
|
}
|
|
|
|
|
2018-08-27 08:50:40 +00:00
|
|
|
QString widthString = valueOfKeyInMapOrEmpty(part, "deformWidth");
|
2019-02-18 12:57:18 +00:00
|
|
|
if (!widthString.isEmpty()) {
|
|
|
|
deformWidth = widthString.toFloat();
|
|
|
|
}
|
2018-04-26 02:23:22 +00:00
|
|
|
|
2018-10-09 02:19:12 +00:00
|
|
|
QUuid materialId;
|
|
|
|
QString materialIdString = valueOfKeyInMapOrEmpty(part, "materialId");
|
|
|
|
if (!materialIdString.isEmpty())
|
|
|
|
materialId = QUuid(materialIdString);
|
2018-10-04 12:51:01 +00:00
|
|
|
|
2019-05-25 23:24:24 +00:00
|
|
|
float colorSolubility = 0;
|
|
|
|
QString colorSolubilityString = valueOfKeyInMapOrEmpty(part, "colorSolubility");
|
|
|
|
if (!colorSolubilityString.isEmpty())
|
|
|
|
colorSolubility = colorSolubilityString.toFloat();
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
auto &partCache = m_cacheContext->parts[partIdString];
|
|
|
|
partCache.outcomeNodes.clear();
|
|
|
|
partCache.outcomeNodeVertices.clear();
|
|
|
|
partCache.vertices.clear();
|
|
|
|
partCache.faces.clear();
|
2019-02-23 04:06:24 +00:00
|
|
|
partCache.previewTriangles.clear();
|
|
|
|
partCache.isSucceed = false;
|
2019-02-18 12:57:18 +00:00
|
|
|
delete partCache.mesh;
|
|
|
|
partCache.mesh = nullptr;
|
2018-11-11 23:21:34 +00:00
|
|
|
|
|
|
|
struct NodeInfo
|
|
|
|
{
|
|
|
|
float radius = 0;
|
|
|
|
QVector3D position;
|
|
|
|
BoneMark boneMark = BoneMark::None;
|
|
|
|
};
|
|
|
|
std::map<QString, NodeInfo> nodeInfos;
|
2019-02-18 12:57:18 +00:00
|
|
|
for (const auto &nodeIdString: m_partNodeIds[partIdString]) {
|
|
|
|
auto findNode = m_snapshot->nodes.find(nodeIdString);
|
2018-08-27 08:50:40 +00:00
|
|
|
if (findNode == m_snapshot->nodes.end()) {
|
2019-02-18 12:57:18 +00:00
|
|
|
qDebug() << "Find node failed:" << nodeIdString;
|
2018-04-11 09:19:27 +00:00
|
|
|
continue;
|
2018-04-07 08:44:39 +00:00
|
|
|
}
|
2018-08-27 08:50:40 +00:00
|
|
|
auto &node = findNode->second;
|
2018-04-07 08:44:39 +00:00
|
|
|
|
2018-08-27 08:50:40 +00:00
|
|
|
float radius = valueOfKeyInMapOrEmpty(node, "radius").toFloat();
|
|
|
|
float x = (valueOfKeyInMapOrEmpty(node, "x").toFloat() - m_mainProfileMiddleX);
|
|
|
|
float y = (m_mainProfileMiddleY - valueOfKeyInMapOrEmpty(node, "y").toFloat());
|
|
|
|
float z = (m_sideProfileMiddleX - valueOfKeyInMapOrEmpty(node, "z").toFloat());
|
2018-11-11 23:21:34 +00:00
|
|
|
|
2018-10-25 00:19:38 +00:00
|
|
|
BoneMark boneMark = BoneMarkFromString(valueOfKeyInMapOrEmpty(node, "boneMark").toUtf8().constData());
|
2018-09-14 09:45:05 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
auto &nodeInfo = nodeInfos[nodeIdString];
|
2018-11-11 23:21:34 +00:00
|
|
|
nodeInfo.position = QVector3D(x, y, z);
|
|
|
|
nodeInfo.radius = radius;
|
|
|
|
nodeInfo.boneMark = boneMark;
|
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
|
2018-11-11 23:21:34 +00:00
|
|
|
std::set<std::pair<QString, QString>> edges;
|
2019-02-18 12:57:18 +00:00
|
|
|
for (const auto &edgeIdString: m_partEdgeIds[partIdString]) {
|
|
|
|
auto findEdge = m_snapshot->edges.find(edgeIdString);
|
2018-11-11 23:21:34 +00:00
|
|
|
if (findEdge == m_snapshot->edges.end()) {
|
2019-02-18 12:57:18 +00:00
|
|
|
qDebug() << "Find edge failed:" << edgeIdString;
|
2018-11-11 23:21:34 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto &edge = findEdge->second;
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
QString fromNodeIdString = valueOfKeyInMapOrEmpty(edge, "from");
|
|
|
|
QString toNodeIdString = valueOfKeyInMapOrEmpty(edge, "to");
|
2018-11-11 23:21:34 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
const auto &findFromNodeInfo = nodeInfos.find(fromNodeIdString);
|
|
|
|
if (findFromNodeInfo == nodeInfos.end()) {
|
|
|
|
qDebug() << "Find from-node info failed:" << fromNodeIdString;
|
|
|
|
continue;
|
|
|
|
}
|
2018-11-11 23:21:34 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
const auto &findToNodeInfo = nodeInfos.find(toNodeIdString);
|
|
|
|
if (findToNodeInfo == nodeInfos.end()) {
|
|
|
|
qDebug() << "Find to-node info failed:" << toNodeIdString;
|
|
|
|
continue;
|
|
|
|
}
|
2018-11-11 23:21:34 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
edges.insert({fromNodeIdString, toNodeIdString});
|
2018-11-11 23:21:34 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
std::map<QString, int> nodeIdStringToIndexMap;
|
|
|
|
std::map<int, QString> nodeIndexToIdStringMap;
|
|
|
|
|
|
|
|
nodemesh::Modifier *modifier = new nodemesh::Modifier;
|
|
|
|
|
|
|
|
QString mirroredPartIdString;
|
|
|
|
QUuid mirroredPartId;
|
|
|
|
if (xMirrored) {
|
|
|
|
mirroredPartId = QUuid().createUuid();
|
|
|
|
mirroredPartIdString = mirroredPartId.toString();
|
|
|
|
m_cacheContext->partMirrorIdMap[mirroredPartIdString] = partIdString;
|
|
|
|
}
|
2018-11-11 23:21:34 +00:00
|
|
|
|
|
|
|
for (const auto &nodeIt: nodeInfos) {
|
2019-02-18 12:57:18 +00:00
|
|
|
const auto &nodeIdString = nodeIt.first;
|
2018-11-11 23:21:34 +00:00
|
|
|
const auto &nodeInfo = nodeIt.second;
|
2019-02-21 22:48:15 +00:00
|
|
|
size_t nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate);
|
2019-02-18 12:57:18 +00:00
|
|
|
nodeIdStringToIndexMap[nodeIdString] = nodeIndex;
|
|
|
|
nodeIndexToIdStringMap[nodeIndex] = nodeIdString;
|
2018-11-11 23:21:34 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
OutcomeNode outcomeNode;
|
|
|
|
outcomeNode.partId = QUuid(partIdString);
|
|
|
|
outcomeNode.nodeId = QUuid(nodeIdString);
|
|
|
|
outcomeNode.origin = nodeInfo.position;
|
|
|
|
outcomeNode.radius = nodeInfo.radius;
|
|
|
|
outcomeNode.color = partColor;
|
|
|
|
outcomeNode.materialId = materialId;
|
2019-05-25 23:24:24 +00:00
|
|
|
outcomeNode.colorSolubility = colorSolubility;
|
2019-02-18 12:57:18 +00:00
|
|
|
outcomeNode.boneMark = nodeInfo.boneMark;
|
|
|
|
outcomeNode.mirroredByPartId = mirroredPartIdString;
|
|
|
|
partCache.outcomeNodes.push_back(outcomeNode);
|
2018-09-14 09:45:05 +00:00
|
|
|
if (xMirrored) {
|
2019-02-18 12:57:18 +00:00
|
|
|
outcomeNode.partId = mirroredPartId;
|
|
|
|
outcomeNode.mirrorFromPartId = QUuid(partId);
|
|
|
|
outcomeNode.mirroredByPartId = QUuid();
|
|
|
|
outcomeNode.origin.setX(-nodeInfo.position.x());
|
|
|
|
partCache.outcomeNodes.push_back(outcomeNode);
|
2018-09-14 09:45:05 +00:00
|
|
|
}
|
2018-04-07 08:44:39 +00:00
|
|
|
}
|
|
|
|
|
2018-11-11 23:21:34 +00:00
|
|
|
for (const auto &edgeIt: edges) {
|
2019-02-18 12:57:18 +00:00
|
|
|
const QString &fromNodeIdString = edgeIt.first;
|
|
|
|
const QString &toNodeIdString = edgeIt.second;
|
2018-06-11 14:24:25 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
auto findFromNodeIndex = nodeIdStringToIndexMap.find(fromNodeIdString);
|
|
|
|
if (findFromNodeIndex == nodeIdStringToIndexMap.end()) {
|
|
|
|
qDebug() << "Find from-node failed:" << fromNodeIdString;
|
2018-08-27 08:50:40 +00:00
|
|
|
continue;
|
|
|
|
}
|
2018-04-30 11:31:09 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
auto findToNodeIndex = nodeIdStringToIndexMap.find(toNodeIdString);
|
|
|
|
if (findToNodeIndex == nodeIdStringToIndexMap.end()) {
|
|
|
|
qDebug() << "Find to-node failed:" << toNodeIdString;
|
2018-08-27 08:50:40 +00:00
|
|
|
continue;
|
2018-04-30 11:31:09 +00:00
|
|
|
}
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
modifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
2018-04-11 09:37:28 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
if (subdived)
|
|
|
|
modifier->subdivide();
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
if (rounded)
|
|
|
|
modifier->roundEnd();
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
modifier->finalize();
|
|
|
|
|
|
|
|
nodemesh::Builder *builder = new nodemesh::Builder;
|
|
|
|
builder->setDeformThickness(deformThickness);
|
|
|
|
builder->setDeformWidth(deformWidth);
|
2019-02-21 22:48:15 +00:00
|
|
|
builder->setCutRotation(cutRotation);
|
2019-05-20 13:38:01 +00:00
|
|
|
if (PartBase::YZ == base) {
|
|
|
|
builder->enableBaseNormalOnX(false);
|
2019-06-24 22:44:04 +00:00
|
|
|
} else if (PartBase::Average == base) {
|
|
|
|
builder->enableBaseNormalAverage(true);
|
2019-05-20 13:38:01 +00:00
|
|
|
} else if (PartBase::XY == base) {
|
|
|
|
builder->enableBaseNormalOnZ(false);
|
|
|
|
} else if (PartBase::ZX == base) {
|
|
|
|
builder->enableBaseNormalOnY(false);
|
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
|
2019-07-02 21:49:18 +00:00
|
|
|
std::vector<size_t> builderNodeIndices;
|
2019-07-07 10:55:42 +00:00
|
|
|
for (const auto &node: modifier->nodes()) {
|
|
|
|
auto nodeIndex = builder->addNode(node.position, node.radius, node.cutTemplate);
|
|
|
|
builder->setNodeOriginInfo(nodeIndex, node.nearOriginNodeIndex, node.farOriginNodeIndex);
|
|
|
|
builderNodeIndices.push_back(nodeIndex);
|
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
for (const auto &edge: modifier->edges())
|
|
|
|
builder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
|
|
|
|
bool buildSucceed = builder->build();
|
|
|
|
|
2019-07-02 21:49:18 +00:00
|
|
|
for (size_t i = 0; i < modifier->nodes().size(); ++i) {
|
|
|
|
const auto &node = modifier->nodes()[i];
|
|
|
|
if (!node.isOriginal)
|
|
|
|
continue;
|
|
|
|
const QString &nodeIdString = nodeIndexToIdStringMap[node.originNodeIndex];
|
|
|
|
const nodemesh::Builder::CutFaceTransform *cutFaceTransform = builder->nodeAdjustableCutFaceTransform(builderNodeIndices[i]);
|
2019-07-07 06:27:58 +00:00
|
|
|
if (nullptr != cutFaceTransform &&
|
|
|
|
PartTarget::Model == target) {
|
2019-07-02 23:07:46 +00:00
|
|
|
QUuid nodeId = QUuid(nodeIdString);
|
|
|
|
m_cutFaceTransforms->insert({nodeId, *cutFaceTransform});
|
2019-07-07 06:27:58 +00:00
|
|
|
m_nodesCutFaces->insert({nodeId, cutTemplateMapByName});
|
2019-07-02 23:07:46 +00:00
|
|
|
}
|
2019-07-02 21:49:18 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
partCache.vertices = builder->generatedVertices();
|
|
|
|
partCache.faces = builder->generatedFaces();
|
|
|
|
for (size_t i = 0; i < partCache.vertices.size(); ++i) {
|
|
|
|
const auto &position = partCache.vertices[i];
|
|
|
|
const auto &source = builder->generatedVerticesSourceNodeIndices()[i];
|
|
|
|
size_t nodeIndex = modifier->nodes()[source].originNodeIndex;
|
|
|
|
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
|
|
|
|
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
|
|
|
|
2019-05-19 03:21:38 +00:00
|
|
|
bool hasMeshError = false;
|
2019-02-18 12:57:18 +00:00
|
|
|
nodemesh::Combiner::Mesh *mesh = nullptr;
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
if (buildSucceed) {
|
|
|
|
mesh = new nodemesh::Combiner::Mesh(partCache.vertices, partCache.faces);
|
|
|
|
if (!mesh->isNull()) {
|
|
|
|
if (xMirrored) {
|
|
|
|
std::vector<QVector3D> xMirroredVertices;
|
|
|
|
std::vector<std::vector<size_t>> xMirroredFaces;
|
|
|
|
makeXmirror(partCache.vertices, partCache.faces, &xMirroredVertices, &xMirroredFaces);
|
|
|
|
for (size_t i = 0; i < xMirroredVertices.size(); ++i) {
|
|
|
|
const auto &position = xMirroredVertices[i];
|
|
|
|
const auto &source = builder->generatedVerticesSourceNodeIndices()[i];
|
|
|
|
size_t nodeIndex = modifier->nodes()[source].originNodeIndex;
|
|
|
|
const auto &nodeIdString = nodeIndexToIdStringMap[nodeIndex];
|
|
|
|
partCache.outcomeNodeVertices.push_back({position, {mirroredPartIdString, nodeIdString}});
|
|
|
|
}
|
|
|
|
size_t xMirrorStart = partCache.vertices.size();
|
|
|
|
for (const auto &vertex: xMirroredVertices)
|
|
|
|
partCache.vertices.push_back(vertex);
|
|
|
|
for (const auto &face: xMirroredFaces) {
|
|
|
|
std::vector<size_t> newFace = face;
|
|
|
|
for (auto &it: newFace)
|
|
|
|
it += xMirrorStart;
|
|
|
|
partCache.faces.push_back(newFace);
|
|
|
|
}
|
|
|
|
nodemesh::Combiner::Mesh *xMirroredMesh = new nodemesh::Combiner::Mesh(xMirroredVertices, xMirroredFaces);
|
|
|
|
nodemesh::Combiner::Mesh *newMesh = combineTwoMeshes(*mesh,
|
|
|
|
*xMirroredMesh, nodemesh::Combiner::Method::Union);
|
|
|
|
delete xMirroredMesh;
|
|
|
|
if (newMesh && !newMesh->isNull()) {
|
|
|
|
delete mesh;
|
|
|
|
mesh = newMesh;
|
|
|
|
} else {
|
2019-05-19 03:21:38 +00:00
|
|
|
hasMeshError = true;
|
2019-02-18 12:57:18 +00:00
|
|
|
qDebug() << "Xmirrored mesh generate failed";
|
|
|
|
delete newMesh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2019-05-19 03:21:38 +00:00
|
|
|
hasMeshError = true;
|
2019-02-18 12:57:18 +00:00
|
|
|
qDebug() << "Mesh built is uncombinable";
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
} else {
|
2019-05-19 03:21:38 +00:00
|
|
|
hasMeshError = true;
|
2019-02-18 12:57:18 +00:00
|
|
|
qDebug() << "Mesh build failed";
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
m_partPreviewMeshes[partId] = nullptr;
|
|
|
|
m_generatedPreviewPartIds.insert(partId);
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2019-02-23 04:06:24 +00:00
|
|
|
std::vector<QVector3D> partPreviewVertices;
|
|
|
|
QColor partPreviewColor = partColor;
|
2019-02-18 12:57:18 +00:00
|
|
|
if (nullptr != mesh) {
|
|
|
|
partCache.mesh = new nodemesh::Combiner::Mesh(*mesh);
|
2019-02-23 04:06:24 +00:00
|
|
|
mesh->fetch(partPreviewVertices, partCache.previewTriangles);
|
|
|
|
partCache.isSucceed = true;
|
|
|
|
}
|
|
|
|
if (partCache.previewTriangles.empty()) {
|
|
|
|
partPreviewVertices = partCache.vertices;
|
|
|
|
nodemesh::triangulate(partPreviewVertices, partCache.faces, partCache.previewTriangles);
|
|
|
|
partPreviewColor = Qt::red;
|
|
|
|
partCache.isSucceed = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
nodemesh::trim(&partPreviewVertices, true);
|
2019-05-19 03:21:38 +00:00
|
|
|
for (auto &it: partPreviewVertices) {
|
|
|
|
it *= 2.0;
|
|
|
|
}
|
2019-02-23 04:06:24 +00:00
|
|
|
std::vector<QVector3D> partPreviewTriangleNormals;
|
|
|
|
for (const auto &face: partCache.previewTriangles) {
|
|
|
|
partPreviewTriangleNormals.push_back(QVector3D::normal(
|
|
|
|
partPreviewVertices[face[0]],
|
|
|
|
partPreviewVertices[face[1]],
|
|
|
|
partPreviewVertices[face[2]]
|
|
|
|
));
|
|
|
|
}
|
|
|
|
std::vector<std::vector<QVector3D>> partPreviewTriangleVertexNormals;
|
|
|
|
generateSmoothTriangleVertexNormals(partPreviewVertices,
|
|
|
|
partCache.previewTriangles,
|
|
|
|
partPreviewTriangleNormals,
|
|
|
|
&partPreviewTriangleVertexNormals);
|
|
|
|
if (!partCache.previewTriangles.empty()) {
|
2019-05-19 03:21:38 +00:00
|
|
|
if (target == PartTarget::CutFace)
|
|
|
|
partPreviewColor = Theme::red;
|
2019-02-18 12:57:18 +00:00
|
|
|
m_partPreviewMeshes[partId] = new MeshLoader(partPreviewVertices,
|
2019-02-23 04:06:24 +00:00
|
|
|
partCache.previewTriangles,
|
2019-02-18 12:57:18 +00:00
|
|
|
partPreviewTriangleVertexNormals,
|
2019-02-23 04:06:24 +00:00
|
|
|
partPreviewColor);
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
delete builder;
|
|
|
|
delete modifier;
|
|
|
|
|
|
|
|
if (mesh && mesh->isNull()) {
|
|
|
|
delete mesh;
|
|
|
|
mesh = nullptr;
|
2018-04-11 06:15:11 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
if (isDisabled) {
|
|
|
|
delete mesh;
|
|
|
|
mesh = nullptr;
|
|
|
|
}
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2019-05-19 03:21:38 +00:00
|
|
|
if (target != PartTarget::Model) {
|
|
|
|
delete mesh;
|
|
|
|
mesh = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasMeshError && target == PartTarget::Model) {
|
|
|
|
m_isSucceed = false;
|
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
return mesh;
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
|
|
|
|
2019-02-20 22:57:15 +00:00
|
|
|
const std::map<QString, QString> *MeshGenerator::findComponent(const QString &componentIdString)
|
|
|
|
{
|
|
|
|
const std::map<QString, QString> *component = &m_snapshot->rootComponent;
|
|
|
|
if (componentIdString != QUuid().toString()) {
|
|
|
|
auto findComponent = m_snapshot->components.find(componentIdString);
|
|
|
|
if (findComponent == m_snapshot->components.end()) {
|
|
|
|
qDebug() << "Component not found:" << componentIdString;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return &findComponent->second;
|
|
|
|
}
|
|
|
|
return component;
|
|
|
|
}
|
|
|
|
|
|
|
|
CombineMode MeshGenerator::componentCombineMode(const std::map<QString, QString> *component)
|
|
|
|
{
|
|
|
|
if (nullptr == component)
|
|
|
|
return CombineMode::Normal;
|
|
|
|
CombineMode combineMode = CombineModeFromString(valueOfKeyInMapOrEmpty(*component, "combineMode").toUtf8().constData());
|
|
|
|
if (combineMode == CombineMode::Normal) {
|
|
|
|
if (isTrueValueString(valueOfKeyInMapOrEmpty(*component, "inverse")))
|
|
|
|
combineMode = CombineMode::Inversion;
|
|
|
|
}
|
|
|
|
return combineMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString MeshGenerator::componentColorName(const std::map<QString, QString> *component)
|
|
|
|
{
|
|
|
|
if (nullptr == component)
|
|
|
|
return QString();
|
|
|
|
QString linkDataType = valueOfKeyInMapOrEmpty(*component, "linkDataType");
|
|
|
|
if ("partId" == linkDataType) {
|
|
|
|
QString partIdString = valueOfKeyInMapOrEmpty(*component, "linkData");
|
|
|
|
auto findPart = m_snapshot->parts.find(partIdString);
|
|
|
|
if (findPart == m_snapshot->parts.end()) {
|
|
|
|
qDebug() << "Find part failed:" << partIdString;
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
auto &part = findPart->second;
|
2019-05-25 23:24:24 +00:00
|
|
|
QString colorSolubility = valueOfKeyInMapOrEmpty(part, "colorSolubility");
|
|
|
|
if (!colorSolubility.isEmpty()) {
|
|
|
|
return QString("+");
|
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
QString colorName = valueOfKeyInMapOrEmpty(part, "color");
|
|
|
|
if (colorName.isEmpty())
|
|
|
|
return QString("-");
|
|
|
|
return colorName;
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode)
|
2018-08-27 08:50:40 +00:00
|
|
|
{
|
2019-02-18 12:57:18 +00:00
|
|
|
nodemesh::Combiner::Mesh *mesh = nullptr;
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
QUuid componentId;
|
2018-08-27 08:50:40 +00:00
|
|
|
const std::map<QString, QString> *component = &m_snapshot->rootComponent;
|
2019-02-18 12:57:18 +00:00
|
|
|
if (componentIdString != QUuid().toString()) {
|
|
|
|
componentId = QUuid(componentIdString);
|
|
|
|
auto findComponent = m_snapshot->components.find(componentIdString);
|
2018-08-27 08:50:40 +00:00
|
|
|
if (findComponent == m_snapshot->components.end()) {
|
2019-02-18 12:57:18 +00:00
|
|
|
qDebug() << "Component not found:" << componentIdString;
|
2018-08-27 08:50:40 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
component = &findComponent->second;
|
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
|
2019-02-20 22:57:15 +00:00
|
|
|
*combineMode = componentCombineMode(component);
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
auto &componentCache = m_cacheContext->components[componentIdString];
|
2018-09-06 15:04:59 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
if (m_cacheEnabled) {
|
|
|
|
if (m_dirtyComponentIds.find(componentIdString) == m_dirtyComponentIds.end()) {
|
2019-03-16 06:02:45 +00:00
|
|
|
if (nullptr != componentCache.mesh)
|
|
|
|
return new nodemesh::Combiner::Mesh(*componentCache.mesh);
|
2019-02-18 12:57:18 +00:00
|
|
|
}
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
componentCache.sharedQuadEdges.clear();
|
|
|
|
componentCache.noneSeamVertices.clear();
|
|
|
|
componentCache.outcomeNodes.clear();
|
|
|
|
componentCache.outcomeNodeVertices.clear();
|
|
|
|
delete componentCache.mesh;
|
|
|
|
componentCache.mesh = nullptr;
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2018-09-06 15:04:59 +00:00
|
|
|
QString linkDataType = valueOfKeyInMapOrEmpty(*component, "linkDataType");
|
|
|
|
if ("partId" == linkDataType) {
|
2019-02-18 12:57:18 +00:00
|
|
|
QString partIdString = valueOfKeyInMapOrEmpty(*component, "linkData");
|
|
|
|
mesh = combinePartMesh(partIdString);
|
|
|
|
|
|
|
|
const auto &partCache = m_cacheContext->parts[partIdString];
|
|
|
|
for (const auto &vertex: partCache.vertices)
|
|
|
|
componentCache.noneSeamVertices.insert(vertex);
|
|
|
|
collectSharedQuadEdges(partCache.vertices, partCache.faces, &componentCache.sharedQuadEdges);
|
|
|
|
for (const auto &it: partCache.outcomeNodes)
|
|
|
|
componentCache.outcomeNodes.push_back(it);
|
|
|
|
for (const auto &it: partCache.outcomeNodeVertices)
|
|
|
|
componentCache.outcomeNodeVertices.push_back(it);
|
2018-09-06 15:04:59 +00:00
|
|
|
} else {
|
2019-02-20 22:57:15 +00:00
|
|
|
std::vector<std::pair<CombineMode, std::vector<std::pair<QString, QString>>>> combineGroups;
|
|
|
|
// Firstly, group by combine mode
|
|
|
|
int currentGroupIndex = -1;
|
|
|
|
auto lastCombineMode = CombineMode::Count;
|
2019-05-25 23:24:24 +00:00
|
|
|
bool foundColorSolubilitySetting = false;
|
2019-02-18 12:57:18 +00:00
|
|
|
for (const auto &childIdString: valueOfKeyInMapOrEmpty(*component, "children").split(",")) {
|
|
|
|
if (childIdString.isEmpty())
|
2018-09-06 15:04:59 +00:00
|
|
|
continue;
|
2019-02-20 22:57:15 +00:00
|
|
|
const auto &child = findComponent(childIdString);
|
|
|
|
QString colorName = componentColorName(child);
|
2019-05-25 23:24:24 +00:00
|
|
|
if (colorName == "+") {
|
|
|
|
foundColorSolubilitySetting = true;
|
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
auto combineMode = componentCombineMode(child);
|
|
|
|
if (lastCombineMode != combineMode || lastCombineMode == CombineMode::Inversion) {
|
|
|
|
qDebug() << "New group[" << currentGroupIndex << "] for combine mode[" << CombineModeToString(combineMode) << "]";
|
|
|
|
combineGroups.push_back({combineMode, {}});
|
|
|
|
++currentGroupIndex;
|
|
|
|
lastCombineMode = combineMode;
|
2018-11-15 14:01:20 +00:00
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
if (-1 == currentGroupIndex) {
|
|
|
|
qDebug() << "Should not happen: -1 == currentGroupIndex";
|
2018-09-06 15:04:59 +00:00
|
|
|
continue;
|
2019-02-18 12:57:18 +00:00
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
combineGroups[currentGroupIndex].second.push_back({childIdString, colorName});
|
|
|
|
}
|
|
|
|
// Secondly, sub group by color
|
|
|
|
std::vector<std::pair<nodemesh::Combiner::Mesh *, CombineMode>> groupMeshes;
|
|
|
|
for (const auto &group: combineGroups) {
|
|
|
|
std::set<size_t> used;
|
|
|
|
std::vector<std::vector<QString>> componentIdStrings;
|
|
|
|
int currentSubGroupIndex = -1;
|
|
|
|
auto lastColorName = QString();
|
|
|
|
for (size_t i = 0; i < group.second.size(); ++i) {
|
|
|
|
if (used.find(i) != used.end())
|
|
|
|
continue;
|
|
|
|
const auto &colorName = group.second[i].second;
|
|
|
|
if (lastColorName != colorName || lastColorName.isEmpty()) {
|
|
|
|
qDebug() << "New sub group[" << currentSubGroupIndex << "] for color[" << colorName << "]";
|
|
|
|
componentIdStrings.push_back({});
|
|
|
|
++currentSubGroupIndex;
|
|
|
|
lastColorName = colorName;
|
2018-09-06 15:04:59 +00:00
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
if (-1 == currentSubGroupIndex) {
|
|
|
|
qDebug() << "Should not happen: -1 == currentSubGroupIndex";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
used.insert(i);
|
|
|
|
componentIdStrings[currentSubGroupIndex].push_back(group.second[i].first);
|
|
|
|
if (colorName.isEmpty())
|
|
|
|
continue;
|
|
|
|
for (size_t j = i + 1; j < group.second.size(); ++j) {
|
|
|
|
if (used.find(j) != used.end())
|
|
|
|
continue;
|
|
|
|
const auto &otherColorName = group.second[j].second;
|
|
|
|
if (otherColorName.isEmpty())
|
|
|
|
continue;
|
|
|
|
if (otherColorName != colorName)
|
|
|
|
continue;
|
|
|
|
used.insert(j);
|
|
|
|
componentIdStrings[currentSubGroupIndex].push_back(group.second[j].first);
|
2018-09-06 15:04:59 +00:00
|
|
|
}
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
std::vector<std::pair<nodemesh::Combiner::Mesh *, CombineMode>> multipleMeshes;
|
|
|
|
for (const auto &it: componentIdStrings) {
|
|
|
|
nodemesh::Combiner::Mesh *childMesh = combineComponentChildGroupMesh(it, componentCache);
|
2019-05-11 12:01:13 +00:00
|
|
|
if (nullptr == childMesh)
|
|
|
|
continue;
|
|
|
|
if (childMesh->isNull()) {
|
|
|
|
delete childMesh;
|
|
|
|
continue;
|
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
multipleMeshes.push_back({childMesh, CombineMode::Normal});
|
|
|
|
}
|
2019-05-25 23:24:24 +00:00
|
|
|
nodemesh::Combiner::Mesh *subGroupMesh = combineMultipleMeshes(multipleMeshes, foundColorSolubilitySetting);
|
2019-05-11 12:01:13 +00:00
|
|
|
if (nullptr == subGroupMesh)
|
|
|
|
continue;
|
2019-02-20 22:57:15 +00:00
|
|
|
groupMeshes.push_back({subGroupMesh, group.first});
|
2018-09-06 15:04:59 +00:00
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
mesh = combineMultipleMeshes(groupMeshes, false);
|
2018-09-06 15:04:59 +00:00
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
if (nullptr != mesh)
|
|
|
|
componentCache.mesh = new nodemesh::Combiner::Mesh(*mesh);
|
|
|
|
|
|
|
|
if (nullptr != mesh && mesh->isNull()) {
|
|
|
|
delete mesh;
|
|
|
|
mesh = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mesh;
|
|
|
|
}
|
|
|
|
|
2019-02-20 22:57:15 +00:00
|
|
|
nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector<std::pair<nodemesh::Combiner::Mesh *, CombineMode>> &multipleMeshes, bool recombine)
|
|
|
|
{
|
|
|
|
nodemesh::Combiner::Mesh *mesh = nullptr;
|
|
|
|
for (const auto &it: multipleMeshes) {
|
|
|
|
const auto &childCombineMode = it.second;
|
|
|
|
nodemesh::Combiner::Mesh *subMesh = it.first;
|
2019-06-24 22:44:04 +00:00
|
|
|
//qDebug() << "Combine mode:" << CombineModeToString(childCombineMode);
|
2019-02-20 22:57:15 +00:00
|
|
|
if (nullptr == subMesh) {
|
|
|
|
qDebug() << "Child mesh is null";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (subMesh->isNull()) {
|
|
|
|
qDebug() << "Child mesh is uncombinable";
|
|
|
|
delete subMesh;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (nullptr == mesh) {
|
|
|
|
//if (childCombineMode == CombineMode::Inversion) {
|
|
|
|
// delete subMesh;
|
|
|
|
//} else {
|
|
|
|
mesh = subMesh;
|
|
|
|
//}
|
|
|
|
} else {
|
|
|
|
nodemesh::Combiner::Mesh *newMesh = combineTwoMeshes(*mesh,
|
|
|
|
*subMesh,
|
|
|
|
childCombineMode == CombineMode::Inversion ?
|
|
|
|
nodemesh::Combiner::Method::Diff : nodemesh::Combiner::Method::Union,
|
|
|
|
recombine);
|
|
|
|
delete subMesh;
|
|
|
|
if (newMesh && !newMesh->isNull()) {
|
|
|
|
delete mesh;
|
|
|
|
mesh = newMesh;
|
|
|
|
} else {
|
|
|
|
m_isSucceed = false;
|
|
|
|
qDebug() << "Mesh combine failed";
|
|
|
|
delete newMesh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-25 23:24:24 +00:00
|
|
|
if (nullptr != mesh && mesh->isNull()) {
|
|
|
|
delete mesh;
|
|
|
|
mesh = nullptr;
|
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
return mesh;
|
|
|
|
}
|
|
|
|
|
|
|
|
nodemesh::Combiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const std::vector<QString> &componentIdStrings, GeneratedComponent &componentCache)
|
|
|
|
{
|
|
|
|
std::vector<std::pair<nodemesh::Combiner::Mesh *, CombineMode>> multipleMeshes;
|
|
|
|
for (const auto &childIdString: componentIdStrings) {
|
|
|
|
CombineMode childCombineMode = CombineMode::Normal;
|
|
|
|
nodemesh::Combiner::Mesh *subMesh = combineComponentMesh(childIdString, &childCombineMode);
|
2019-05-11 12:01:13 +00:00
|
|
|
|
|
|
|
if (CombineMode::Uncombined == childCombineMode) {
|
|
|
|
delete subMesh;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-02-20 22:57:15 +00:00
|
|
|
const auto &childComponentCache = m_cacheContext->components[childIdString];
|
|
|
|
for (const auto &vertex: childComponentCache.noneSeamVertices)
|
|
|
|
componentCache.noneSeamVertices.insert(vertex);
|
|
|
|
for (const auto &it: childComponentCache.sharedQuadEdges)
|
|
|
|
componentCache.sharedQuadEdges.insert(it);
|
|
|
|
for (const auto &it: childComponentCache.outcomeNodes)
|
|
|
|
componentCache.outcomeNodes.push_back(it);
|
|
|
|
for (const auto &it: childComponentCache.outcomeNodeVertices)
|
|
|
|
componentCache.outcomeNodeVertices.push_back(it);
|
2019-05-25 23:24:24 +00:00
|
|
|
|
|
|
|
if (nullptr == subMesh || subMesh->isNull()) {
|
|
|
|
delete subMesh;
|
|
|
|
continue;
|
|
|
|
}
|
2019-02-20 22:57:15 +00:00
|
|
|
|
|
|
|
multipleMeshes.push_back({subMesh, childCombineMode});
|
|
|
|
}
|
|
|
|
return combineMultipleMeshes(multipleMeshes);
|
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
nodemesh::Combiner::Mesh *MeshGenerator::combineTwoMeshes(const nodemesh::Combiner::Mesh &first, const nodemesh::Combiner::Mesh &second,
|
2019-02-20 22:57:15 +00:00
|
|
|
nodemesh::Combiner::Method method,
|
|
|
|
bool recombine)
|
2019-02-18 12:57:18 +00:00
|
|
|
{
|
|
|
|
if (first.isNull() || second.isNull())
|
|
|
|
return nullptr;
|
|
|
|
std::vector<std::pair<nodemesh::Combiner::Source, size_t>> combinedVerticesSources;
|
|
|
|
nodemesh::Combiner::Mesh *newMesh = nodemesh::Combiner::combine(first,
|
|
|
|
second,
|
|
|
|
method,
|
|
|
|
&combinedVerticesSources);
|
|
|
|
if (nullptr == newMesh)
|
|
|
|
return nullptr;
|
2019-02-20 22:57:15 +00:00
|
|
|
if (!newMesh->isNull() && recombine) {
|
2019-02-18 12:57:18 +00:00
|
|
|
nodemesh::Recombiner recombiner;
|
|
|
|
std::vector<QVector3D> combinedVertices;
|
|
|
|
std::vector<std::vector<size_t>> combinedFaces;
|
|
|
|
newMesh->fetch(combinedVertices, combinedFaces);
|
|
|
|
recombiner.setVertices(&combinedVertices, &combinedVerticesSources);
|
|
|
|
recombiner.setFaces(&combinedFaces);
|
|
|
|
if (recombiner.recombine()) {
|
|
|
|
if (nodemesh::isManifold(recombiner.regeneratedFaces())) {
|
|
|
|
nodemesh::Combiner::Mesh *reMesh = new nodemesh::Combiner::Mesh(recombiner.regeneratedVertices(), recombiner.regeneratedFaces(), false);
|
|
|
|
if (!reMesh->isNull() && !reMesh->isSelfIntersected()) {
|
|
|
|
delete newMesh;
|
|
|
|
newMesh = reMesh;
|
|
|
|
} else {
|
|
|
|
delete reMesh;
|
2018-09-06 15:04:59 +00:00
|
|
|
}
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
return newMesh;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MeshGenerator::makeXmirror(const std::vector<QVector3D> &sourceVertices, const std::vector<std::vector<size_t>> &sourceFaces,
|
|
|
|
std::vector<QVector3D> *destVertices, std::vector<std::vector<size_t>> *destFaces)
|
|
|
|
{
|
|
|
|
for (const auto &mirrorFrom: sourceVertices) {
|
|
|
|
destVertices->push_back(QVector3D(-mirrorFrom.x(), mirrorFrom.y(), mirrorFrom.z()));
|
|
|
|
}
|
|
|
|
std::vector<std::vector<size_t>> newFaces;
|
|
|
|
for (const auto &mirrorFrom: sourceFaces) {
|
|
|
|
auto newFace = mirrorFrom;
|
|
|
|
std::reverse(newFace.begin(), newFace.end());
|
|
|
|
destFaces->push_back(newFace);
|
2019-01-07 13:03:42 +00:00
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MeshGenerator::collectSharedQuadEdges(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &faces,
|
|
|
|
std::set<std::pair<nodemesh::PositionKey, nodemesh::PositionKey>> *sharedQuadEdges)
|
|
|
|
{
|
|
|
|
for (const auto &face: faces) {
|
|
|
|
if (face.size() != 4)
|
|
|
|
continue;
|
|
|
|
sharedQuadEdges->insert({
|
|
|
|
nodemesh::PositionKey(vertices[face[0]]),
|
|
|
|
nodemesh::PositionKey(vertices[face[2]])
|
|
|
|
});
|
|
|
|
sharedQuadEdges->insert({
|
|
|
|
nodemesh::PositionKey(vertices[face[1]]),
|
|
|
|
nodemesh::PositionKey(vertices[face[3]])
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MeshGenerator::setGeneratedCacheContext(GeneratedCacheContext *cacheContext)
|
|
|
|
{
|
|
|
|
m_cacheContext = cacheContext;
|
|
|
|
}
|
|
|
|
|
2019-05-30 10:28:55 +00:00
|
|
|
void MeshGenerator::setSmoothShadingThresholdAngleDegrees(float degrees)
|
|
|
|
{
|
|
|
|
m_smoothShadingThresholdAngleDegrees = degrees;
|
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
void MeshGenerator::process()
|
|
|
|
{
|
|
|
|
generate();
|
2019-01-07 13:03:42 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
this->moveToThread(QGuiApplication::instance()->thread());
|
|
|
|
emit finished();
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
|
|
|
|
2018-10-09 02:19:12 +00:00
|
|
|
void MeshGenerator::generate()
|
2018-08-27 08:50:40 +00:00
|
|
|
{
|
|
|
|
if (nullptr == m_snapshot)
|
|
|
|
return;
|
2019-01-07 13:03:42 +00:00
|
|
|
|
|
|
|
m_isSucceed = true;
|
2018-08-27 08:50:40 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
QElapsedTimer countTimeConsumed;
|
|
|
|
countTimeConsumed.start();
|
|
|
|
|
2018-10-25 00:19:38 +00:00
|
|
|
m_outcome = new Outcome;
|
2019-07-02 21:49:18 +00:00
|
|
|
m_cutFaceTransforms = new std::map<QUuid, nodemesh::Builder::CutFaceTransform>;
|
2019-07-07 06:27:58 +00:00
|
|
|
m_nodesCutFaces = new std::map<QUuid, std::map<QString, QVector2D>>;
|
2018-08-27 08:50:40 +00:00
|
|
|
|
|
|
|
bool needDeleteCacheContext = false;
|
|
|
|
if (nullptr == m_cacheContext) {
|
|
|
|
m_cacheContext = new GeneratedCacheContext;
|
|
|
|
needDeleteCacheContext = true;
|
|
|
|
} else {
|
2019-02-18 12:57:18 +00:00
|
|
|
m_cacheEnabled = true;
|
|
|
|
for (auto it = m_cacheContext->parts.begin(); it != m_cacheContext->parts.end(); ) {
|
2018-08-27 08:50:40 +00:00
|
|
|
if (m_snapshot->parts.find(it->first) == m_snapshot->parts.end()) {
|
2018-09-14 09:45:05 +00:00
|
|
|
auto mirrorFrom = m_cacheContext->partMirrorIdMap.find(it->first);
|
|
|
|
if (mirrorFrom != m_cacheContext->partMirrorIdMap.end()) {
|
|
|
|
if (m_snapshot->parts.find(mirrorFrom->second) != m_snapshot->parts.end()) {
|
|
|
|
it++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
m_cacheContext->partMirrorIdMap.erase(mirrorFrom);
|
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
it = m_cacheContext->parts.erase(it);
|
2018-11-15 14:01:20 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
it++;
|
|
|
|
}
|
2019-02-18 12:57:18 +00:00
|
|
|
for (auto it = m_cacheContext->components.begin(); it != m_cacheContext->components.end(); ) {
|
2018-09-06 15:04:59 +00:00
|
|
|
if (m_snapshot->components.find(it->first) == m_snapshot->components.end()) {
|
2019-02-18 12:57:18 +00:00
|
|
|
it = m_cacheContext->components.erase(it);
|
2018-09-06 15:04:59 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
it++;
|
|
|
|
}
|
2018-04-07 08:44:39 +00:00
|
|
|
}
|
|
|
|
|
2018-08-27 08:50:40 +00:00
|
|
|
collectParts();
|
|
|
|
checkDirtyFlags();
|
|
|
|
|
2018-09-06 15:04:59 +00:00
|
|
|
m_dirtyComponentIds.insert(QUuid().toString());
|
|
|
|
|
2018-08-27 08:50:40 +00:00
|
|
|
m_mainProfileMiddleX = valueOfKeyInMapOrEmpty(m_snapshot->canvas, "originX").toFloat();
|
|
|
|
m_mainProfileMiddleY = valueOfKeyInMapOrEmpty(m_snapshot->canvas, "originY").toFloat();
|
|
|
|
m_sideProfileMiddleX = valueOfKeyInMapOrEmpty(m_snapshot->canvas, "originZ").toFloat();
|
|
|
|
|
2018-11-15 14:01:20 +00:00
|
|
|
CombineMode combineMode;
|
2019-02-18 12:57:18 +00:00
|
|
|
auto combinedMesh = combineComponentMesh(QUuid().toString(), &combineMode);
|
2018-04-07 08:44:39 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
const auto &componentCache = m_cacheContext->components[QUuid().toString()];
|
2018-04-07 08:44:39 +00:00
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
std::vector<QVector3D> combinedVertices;
|
|
|
|
std::vector<std::vector<size_t>> combinedFaces;
|
|
|
|
if (nullptr != combinedMesh) {
|
|
|
|
combinedMesh->fetch(combinedVertices, combinedFaces);
|
|
|
|
|
|
|
|
size_t totalAffectedNum = 0;
|
|
|
|
size_t affectedNum = 0;
|
|
|
|
do {
|
|
|
|
std::vector<QVector3D> weldedVertices;
|
|
|
|
std::vector<std::vector<size_t>> weldedFaces;
|
|
|
|
affectedNum = nodemesh::weldSeam(combinedVertices, combinedFaces,
|
|
|
|
0.025, componentCache.noneSeamVertices,
|
|
|
|
weldedVertices, weldedFaces);
|
|
|
|
combinedVertices = weldedVertices;
|
|
|
|
combinedFaces = weldedFaces;
|
|
|
|
totalAffectedNum += affectedNum;
|
|
|
|
} while (affectedNum > 0);
|
|
|
|
qDebug() << "Total weld affected triangles:" << totalAffectedNum;
|
|
|
|
|
|
|
|
recoverQuads(combinedVertices, combinedFaces, componentCache.sharedQuadEdges, m_outcome->triangleAndQuads);
|
|
|
|
|
|
|
|
m_outcome->nodes = componentCache.outcomeNodes;
|
|
|
|
m_outcome->nodeVertices = componentCache.outcomeNodeVertices;
|
|
|
|
m_outcome->vertices = combinedVertices;
|
|
|
|
m_outcome->triangles = combinedFaces;
|
2019-02-23 04:06:24 +00:00
|
|
|
}
|
|
|
|
|
2019-05-11 12:01:13 +00:00
|
|
|
// Recursively check uncombined components
|
|
|
|
collectUncombinedComponent(QUuid().toString());
|
|
|
|
|
2019-05-30 10:28:55 +00:00
|
|
|
auto postprocessOutcome = [this](Outcome *outcome) {
|
2019-02-23 04:06:24 +00:00
|
|
|
std::vector<QVector3D> combinedFacesNormals;
|
|
|
|
for (const auto &face: outcome->triangles) {
|
|
|
|
combinedFacesNormals.push_back(QVector3D::normal(
|
|
|
|
outcome->vertices[face[0]],
|
|
|
|
outcome->vertices[face[1]],
|
|
|
|
outcome->vertices[face[2]]
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
outcome->triangleNormals = combinedFacesNormals;
|
2019-02-18 12:57:18 +00:00
|
|
|
|
2018-10-26 23:04:45 +00:00
|
|
|
std::vector<std::pair<QUuid, QUuid>> sourceNodes;
|
2019-02-23 04:06:24 +00:00
|
|
|
triangleSourceNodeResolve(*outcome, sourceNodes);
|
|
|
|
outcome->setTriangleSourceNodes(sourceNodes);
|
2018-10-26 23:04:45 +00:00
|
|
|
|
2019-02-20 22:57:15 +00:00
|
|
|
std::map<std::pair<QUuid, QUuid>, QColor> sourceNodeToColorMap;
|
2019-02-23 04:06:24 +00:00
|
|
|
for (const auto &node: outcome->nodes)
|
2019-02-20 22:57:15 +00:00
|
|
|
sourceNodeToColorMap.insert({{node.partId, node.nodeId}, node.color});
|
|
|
|
|
2019-02-23 04:06:24 +00:00
|
|
|
outcome->triangleColors.resize(outcome->triangles.size(), Qt::white);
|
|
|
|
const std::vector<std::pair<QUuid, QUuid>> *triangleSourceNodes = outcome->triangleSourceNodes();
|
2019-02-20 22:57:15 +00:00
|
|
|
if (nullptr != triangleSourceNodes) {
|
2019-02-23 04:06:24 +00:00
|
|
|
for (size_t triangleIndex = 0; triangleIndex < outcome->triangles.size(); triangleIndex++) {
|
2019-02-20 22:57:15 +00:00
|
|
|
const auto &source = (*triangleSourceNodes)[triangleIndex];
|
2019-02-23 04:06:24 +00:00
|
|
|
outcome->triangleColors[triangleIndex] = sourceNodeToColorMap[source];
|
2019-02-20 22:57:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
std::vector<std::vector<QVector3D>> triangleVertexNormals;
|
2019-02-23 04:06:24 +00:00
|
|
|
generateSmoothTriangleVertexNormals(outcome->vertices,
|
|
|
|
outcome->triangles,
|
|
|
|
outcome->triangleNormals,
|
2019-02-18 12:57:18 +00:00
|
|
|
&triangleVertexNormals);
|
2019-02-23 04:06:24 +00:00
|
|
|
outcome->setTriangleVertexNormals(triangleVertexNormals);
|
|
|
|
};
|
|
|
|
|
|
|
|
postprocessOutcome(m_outcome);
|
|
|
|
|
|
|
|
m_resultMesh = new MeshLoader(*m_outcome);
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
delete combinedMesh;
|
|
|
|
|
2018-08-27 08:50:40 +00:00
|
|
|
if (needDeleteCacheContext) {
|
|
|
|
delete m_cacheContext;
|
|
|
|
m_cacheContext = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << "The mesh generation took" << countTimeConsumed.elapsed() << "milliseconds";
|
2018-10-09 02:19:12 +00:00
|
|
|
}
|
|
|
|
|
2019-05-11 12:01:13 +00:00
|
|
|
void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
|
|
|
|
{
|
|
|
|
const auto &component = findComponent(componentIdString);
|
|
|
|
if (CombineMode::Uncombined == componentCombineMode(component)) {
|
|
|
|
const auto &componentCache = m_cacheContext->components[componentIdString];
|
|
|
|
if (nullptr == componentCache.mesh || componentCache.mesh->isNull()) {
|
|
|
|
qDebug() << "Uncombined mesh is null";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_outcome->nodes.insert(m_outcome->nodes.end(), componentCache.outcomeNodes.begin(), componentCache.outcomeNodes.end());
|
|
|
|
m_outcome->nodeVertices.insert(m_outcome->nodeVertices.end(), componentCache.outcomeNodeVertices.begin(), componentCache.outcomeNodeVertices.end());
|
|
|
|
|
|
|
|
std::vector<QVector3D> uncombinedVertices;
|
|
|
|
std::vector<std::vector<size_t>> uncombinedFaces;
|
|
|
|
componentCache.mesh->fetch(uncombinedVertices, uncombinedFaces);
|
|
|
|
std::vector<std::vector<size_t>> uncombinedTriangleAndQuads;
|
|
|
|
|
|
|
|
recoverQuads(uncombinedVertices, uncombinedFaces, componentCache.sharedQuadEdges, uncombinedTriangleAndQuads);
|
|
|
|
|
|
|
|
auto vertexStartIndex = m_outcome->vertices.size();
|
|
|
|
auto updateVertexIndices = [=](std::vector<std::vector<size_t>> &faces) {
|
|
|
|
for (auto &it: faces) {
|
|
|
|
for (auto &subIt: it)
|
|
|
|
subIt += vertexStartIndex;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
updateVertexIndices(uncombinedFaces);
|
|
|
|
updateVertexIndices(uncombinedTriangleAndQuads);
|
|
|
|
|
|
|
|
m_outcome->vertices.insert(m_outcome->vertices.end(), uncombinedVertices.begin(), uncombinedVertices.end());
|
|
|
|
m_outcome->triangles.insert(m_outcome->triangles.end(), uncombinedFaces.begin(), uncombinedFaces.end());
|
|
|
|
m_outcome->triangleAndQuads.insert(m_outcome->triangleAndQuads.end(), uncombinedTriangleAndQuads.begin(), uncombinedTriangleAndQuads.end());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (const auto &childIdString: valueOfKeyInMapOrEmpty(*component, "children").split(",")) {
|
|
|
|
if (childIdString.isEmpty())
|
|
|
|
continue;
|
|
|
|
collectUncombinedComponent(childIdString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-18 12:57:18 +00:00
|
|
|
void MeshGenerator::generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
|
|
|
|
const std::vector<QVector3D> &triangleNormals,
|
|
|
|
std::vector<std::vector<QVector3D>> *triangleVertexNormals)
|
2018-10-09 02:19:12 +00:00
|
|
|
{
|
2019-02-18 12:57:18 +00:00
|
|
|
std::vector<QVector3D> smoothNormals;
|
|
|
|
nodemesh::angleSmooth(vertices,
|
|
|
|
triangles,
|
|
|
|
triangleNormals,
|
2019-05-30 10:28:55 +00:00
|
|
|
m_smoothShadingThresholdAngleDegrees,
|
2019-02-18 12:57:18 +00:00
|
|
|
smoothNormals);
|
|
|
|
triangleVertexNormals->resize(triangles.size(), {
|
|
|
|
QVector3D(), QVector3D(), QVector3D()
|
|
|
|
});
|
|
|
|
size_t index = 0;
|
|
|
|
for (size_t i = 0; i < triangles.size(); ++i) {
|
|
|
|
auto &normals = (*triangleVertexNormals)[i];
|
|
|
|
for (size_t j = 0; j < 3; ++j) {
|
|
|
|
if (index < smoothNormals.size())
|
|
|
|
normals[j] = smoothNormals[index];
|
|
|
|
++index;
|
|
|
|
}
|
|
|
|
}
|
2018-08-27 08:50:40 +00:00
|
|
|
}
|
2019-06-08 07:57:13 +00:00
|
|
|
|
|
|
|
void MeshGenerator::setDefaultPartColor(const QColor &color)
|
|
|
|
{
|
|
|
|
m_defaultPartColor = color;
|
|
|
|
}
|