diff --git a/src/document.cpp b/src/document.cpp index 74e2891f..17764743 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -915,6 +915,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId part["dirty"] = partIt.second.dirty ? "true" : "false"; if (partIt.second.hasColor) part["color"] = partIt.second.color.name(); + if (partIt.second.colorSolubilityAdjusted()) + part["colorSolubility"] = QString::number(partIt.second.colorSolubility); if (partIt.second.deformThicknessAdjusted()) part["deformThickness"] = QString::number(partIt.second.deformThickness); if (partIt.second.deformWidthAdjusted()) @@ -1190,6 +1192,9 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) part.color = QColor(colorIt->second); part.hasColor = true; } + const auto &colorSolubilityIt = partKv.second.find("colorSolubility"); + if (colorSolubilityIt != partKv.second.end()) + part.colorSolubility = colorSolubilityIt->second.toFloat(); const auto &deformThicknessIt = partKv.second.find("deformThickness"); if (deformThicknessIt != partKv.second.end()) part.setDeformThickness(deformThicknessIt->second.toFloat()); @@ -2311,6 +2316,21 @@ void Document::setPartTarget(QUuid partId, PartTarget target) emit skeletonChanged(); } +void Document::setPartColorSolubility(QUuid partId, float solubility) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + if (qFuzzyCompare(part->second.colorSolubility, solubility)) + return; + part->second.colorSolubility = solubility; + part->second.dirty = true; + emit partColorSolubilityChanged(partId); + emit skeletonChanged(); +} + void Document::setPartCutRotation(QUuid partId, float cutRotation) { auto part = partMap.find(partId); diff --git a/src/document.h b/src/document.h index 9b970ee2..0a58f03e 100644 --- a/src/document.h +++ b/src/document.h @@ -406,6 +406,7 @@ signals: void partMaterialIdChanged(QUuid partId); void partChamferStateChanged(QUuid partId); void partTargetChanged(QUuid partId); + void partColorSolubilityChanged(QUuid partId); void componentCombineModeChanged(QUuid componentId); void cleanup(); void originChanged(); @@ -565,6 +566,7 @@ public slots: void setPartMaterialId(QUuid partId, QUuid materialId); void setPartChamferState(QUuid partId, bool chamfered); void setPartTarget(QUuid partId, PartTarget target); + void setPartColorSolubility(QUuid partId, float solubility); void setComponentCombineMode(QUuid componentId, CombineMode combineMode); void moveComponentUp(QUuid componentId); void moveComponentDown(QUuid componentId); diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index bb2794be..e29003af 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -860,6 +860,7 @@ DocumentWindow::DocumentWindow() : connect(m_document, &Document::partCutRotationChanged, partTreeWidget, &PartTreeWidget::partCutRotationChanged); connect(m_document, &Document::partCutFaceChanged, partTreeWidget, &PartTreeWidget::partCutFaceChanged); connect(m_document, &Document::partMaterialIdChanged, partTreeWidget, &PartTreeWidget::partMaterialIdChanged); + connect(m_document, &Document::partColorSolubilityChanged, partTreeWidget, &PartTreeWidget::partColorSolubilityChanged); connect(m_document, &Document::partRemoved, partTreeWidget, &PartTreeWidget::partRemoved); connect(m_document, &Document::cleanup, partTreeWidget, &PartTreeWidget::removeAllContent); connect(m_document, &Document::partChecked, partTreeWidget, &PartTreeWidget::partChecked); diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index c83a36a2..3e72ce8b 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -308,6 +308,11 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt if (!materialIdString.isEmpty()) materialId = QUuid(materialIdString); + float colorSolubility = 0; + QString colorSolubilityString = valueOfKeyInMapOrEmpty(part, "colorSolubility"); + if (!colorSolubilityString.isEmpty()) + colorSolubility = colorSolubilityString.toFloat(); + auto &partCache = m_cacheContext->parts[partIdString]; partCache.outcomeNodes.clear(); partCache.outcomeNodeVertices.clear(); @@ -400,6 +405,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt outcomeNode.radius = nodeInfo.radius; outcomeNode.color = partColor; outcomeNode.materialId = materialId; + outcomeNode.colorSolubility = colorSolubility; outcomeNode.boneMark = nodeInfo.boneMark; outcomeNode.mirroredByPartId = mirroredPartIdString; partCache.outcomeNodes.push_back(outcomeNode); @@ -622,6 +628,10 @@ QString MeshGenerator::componentColorName(const std::map *comp return QString(); } auto &part = findPart->second; + QString colorSolubility = valueOfKeyInMapOrEmpty(part, "colorSolubility"); + if (!colorSolubility.isEmpty()) { + return QString("+"); + } QString colorName = valueOfKeyInMapOrEmpty(part, "color"); if (colorName.isEmpty()) return QString("-"); @@ -682,11 +692,15 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com // Firstly, group by combine mode int currentGroupIndex = -1; auto lastCombineMode = CombineMode::Count; + bool foundColorSolubilitySetting = false; for (const auto &childIdString: valueOfKeyInMapOrEmpty(*component, "children").split(",")) { if (childIdString.isEmpty()) continue; const auto &child = findComponent(childIdString); QString colorName = componentColorName(child); + if (colorName == "+") { + foundColorSolubilitySetting = true; + } auto combineMode = componentCombineMode(child); if (lastCombineMode != combineMode || lastCombineMode == CombineMode::Inversion) { qDebug() << "New group[" << currentGroupIndex << "] for combine mode[" << CombineModeToString(combineMode) << "]"; @@ -748,7 +762,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentMesh(const QString &com } multipleMeshes.push_back({childMesh, CombineMode::Normal}); } - nodemesh::Combiner::Mesh *subGroupMesh = combineMultipleMeshes(multipleMeshes, false); + nodemesh::Combiner::Mesh *subGroupMesh = combineMultipleMeshes(multipleMeshes, foundColorSolubilitySetting); if (nullptr == subGroupMesh) continue; groupMeshes.push_back({subGroupMesh, group.first}); @@ -806,6 +820,10 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector } } } + if (nullptr != mesh && mesh->isNull()) { + delete mesh; + mesh = nullptr; + } return mesh; } @@ -830,6 +848,11 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const st componentCache.outcomeNodes.push_back(it); for (const auto &it: childComponentCache.outcomeNodeVertices) componentCache.outcomeNodeVertices.push_back(it); + + if (nullptr == subMesh || subMesh->isNull()) { + delete subMesh; + continue; + } multipleMeshes.push_back({subMesh, childCombineMode}); } diff --git a/src/outcome.h b/src/outcome.h index ff328118..3714a1aa 100644 --- a/src/outcome.h +++ b/src/outcome.h @@ -18,6 +18,7 @@ struct OutcomeNode QVector3D origin; float radius = 0; QColor color; + float colorSolubility = 0; QUuid materialId; QUuid mirrorFromPartId; QUuid mirroredByPartId; diff --git a/src/parttreewidget.cpp b/src/parttreewidget.cpp index 52f81a13..4bd8bbd1 100644 --- a/src/parttreewidget.cpp +++ b/src/parttreewidget.cpp @@ -1003,6 +1003,17 @@ void PartTreeWidget::partMaterialIdChanged(QUuid partId) widget->updateColorButton(); } +void PartTreeWidget::partColorSolubilityChanged(QUuid partId) +{ + auto item = m_partItemMap.find(partId); + if (item == m_partItemMap.end()) { + qDebug() << "Part item not found:" << partId; + return; + } + PartWidget *widget = (PartWidget *)itemWidget(item->second, 0); + widget->updateColorButton(); +} + void PartTreeWidget::partChecked(QUuid partId) { auto item = m_partItemMap.find(partId); diff --git a/src/parttreewidget.h b/src/parttreewidget.h index eb6a48ea..e9c12f95 100644 --- a/src/parttreewidget.h +++ b/src/parttreewidget.h @@ -67,6 +67,7 @@ public slots: void partCutRotationChanged(QUuid partId); void partCutFaceChanged(QUuid partId); void partMaterialIdChanged(QUuid partId); + void partColorSolubilityChanged(QUuid partId); void partChecked(QUuid partId); void partUnchecked(QUuid partId); void groupChanged(QTreeWidgetItem *item, int column); diff --git a/src/partwidget.cpp b/src/partwidget.cpp index 6cbcd4fd..b93f96b3 100644 --- a/src/partwidget.cpp +++ b/src/partwidget.cpp @@ -166,6 +166,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : connect(this, &PartWidget::setPartCutFaceLinkedId, m_document, &Document::setPartCutFaceLinkedId); connect(this, &PartWidget::setPartColorState, m_document, &Document::setPartColorState); connect(this, &PartWidget::setPartMaterialId, m_document, &Document::setPartMaterialId); + connect(this, &PartWidget::setPartColorSolubility, m_document, &Document::setPartColorSolubility); connect(this, &PartWidget::checkPart, m_document, &Document::checkPart); connect(this, &PartWidget::enableBackgroundBlur, m_document, &Document::enableBackgroundBlur); connect(this, &PartWidget::disableBackgroundBlur, m_document, &Document::disableBackgroundBlur); @@ -375,8 +376,31 @@ void PartWidget::showColorSettingPopup(const QPoint &pos) } }); + FloatNumberWidget *colorSolubilityWidget = new FloatNumberWidget; + colorSolubilityWidget->setItemName(tr("Solubility")); + colorSolubilityWidget->setRange(0.0, 1.0); + colorSolubilityWidget->setValue(part->colorSolubility); + + connect(colorSolubilityWidget, &FloatNumberWidget::valueChanged, [=](float value) { + emit setPartColorSolubility(m_partId, value); + emit groupOperationAdded(); + }); + + QPushButton *colorSolubilityEraser = new QPushButton(QChar(fa::eraser)); + initToolButton(colorSolubilityEraser); + + connect(colorSolubilityEraser, &QPushButton::clicked, [=]() { + colorSolubilityWidget->setValue(0.0); + emit groupOperationAdded(); + }); + + QHBoxLayout *colorSolubilityLayout = new QHBoxLayout; + colorSolubilityLayout->addWidget(colorSolubilityEraser); + colorSolubilityLayout->addWidget(colorSolubilityWidget); + QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(colorLayout); + mainLayout->addLayout(colorSolubilityLayout); if (m_document->materialIdList.empty()) { InfoLabel *infoLabel = new InfoLabel; @@ -712,7 +736,7 @@ void PartWidget::updateColorButton() qDebug() << "Part not found:" << m_partId; return; } - if (part->hasColor || part->materialAdjusted()) + if (part->hasColor || part->materialAdjusted() || part->colorSolubilityAdjusted()) updateButton(m_colorButton, QChar(fa::eyedropper), true); else updateButton(m_colorButton, QChar(fa::eyedropper), false); diff --git a/src/partwidget.h b/src/partwidget.h index acc9367d..8a60fee9 100644 --- a/src/partwidget.h +++ b/src/partwidget.h @@ -25,6 +25,7 @@ signals: void setPartCutFace(QUuid partId, CutFace cutFace); void setPartCutFaceLinkedId(QUuid partId, QUuid linkedId); void setPartMaterialId(QUuid partId, QUuid materialId); + void setPartColorSolubility(QUuid partId, float colorSolubility); void movePartUp(QUuid partId); void movePartDown(QUuid partId); void movePartToTop(QUuid partId); diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 48bb7419..c6774b82 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -93,6 +93,7 @@ public: QUuid cutFaceLinkedId; QUuid materialId; PartTarget target; + float colorSolubility; SkeletonPart(const QUuid &withId=QUuid()) : visible(true), locked(false), @@ -110,7 +111,8 @@ public: dirty(true), cutRotation(0.0), cutFace(CutFace::Quad), - target(PartTarget::Model) + target(PartTarget::Model), + colorSolubility(0.0) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -164,6 +166,10 @@ public: { return deformThicknessAdjusted() || deformWidthAdjusted(); } + bool colorSolubilityAdjusted() const + { + return fabs(colorSolubility - 0.0) >= 0.01; + } bool cutRotationAdjusted() const { return fabs(cutRotation - 0.0) >= 0.01; @@ -206,6 +212,7 @@ public: dirty = other.dirty; materialId = other.materialId; target = other.target; + colorSolubility = other.colorSolubility; } void updatePreviewMesh(MeshLoader *previewMesh) { diff --git a/src/texturegenerator.cpp b/src/texturegenerator.cpp index 7e09ee07..c2f5a943 100644 --- a/src/texturegenerator.cpp +++ b/src/texturegenerator.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "texturegenerator.h" #include "theme.h" #include "util.h" @@ -231,9 +232,11 @@ void TextureGenerator::generate() std::map partColorMap; std::map, const OutcomeNode *> nodeMap; + std::map partColorSolubilityMap; for (const auto &item: m_outcome->nodes) { nodeMap.insert({{item.partId, item.nodeId}, &item}); partColorMap.insert({item.partId, item.color}); + partColorSolubilityMap.insert({item.partId, item.colorSolubility}); } auto createImageBeginTime = countTimeConsumed.elapsed(); @@ -316,6 +319,81 @@ void TextureGenerator::generate() } } + auto drawGradient = [&](const QUuid &partId, size_t triangleIndex, size_t firstVertexIndex, size_t secondVertexIndex, + const QUuid &neighborPartId) { + const std::vector &uv = triangleVertexUvs[triangleIndex]; + const auto &allRects = partUvRects.find(partId); + if (allRects == partUvRects.end()) { + qDebug() << "Found part uv rects failed"; + return; + } + const auto &firstPoint = uv[firstVertexIndex]; + const auto &secondPoint = uv[secondVertexIndex]; + auto edgeLength = firstPoint.distanceToPoint(secondPoint); + auto middlePoint = (firstPoint + secondPoint) / 2.0; + const auto &findColor = partColorMap.find(partId); + if (findColor == partColorMap.end()) + return; + const auto &findNeighborColorSolubility = partColorSolubilityMap.find(neighborPartId); + if (findNeighborColorSolubility == partColorSolubilityMap.end()) + return; + if (qFuzzyIsNull(findNeighborColorSolubility->second)) + return; + const auto &findNeighborColor = partColorMap.find(neighborPartId); + if (findNeighborColor == partColorMap.end()) + return; + for (const auto &it: allRects->second) { + if (it.contains(firstPoint.x(), firstPoint.y()) || + it.contains(secondPoint.x(), secondPoint.y())) { + float finalRadius = (it.width() + it.height()) * 0.5 * findNeighborColorSolubility->second; + if (finalRadius < edgeLength) + finalRadius = edgeLength; + QRadialGradient gradient(QPointF(middlePoint.x() * TextureGenerator::m_textureSize, + middlePoint.y() * TextureGenerator::m_textureSize), + finalRadius * TextureGenerator::m_textureSize); + gradient.setColorAt(0.0, findNeighborColor->second); + gradient.setColorAt(1.0, Qt::transparent); + QRectF fillTarget((middlePoint.x() - finalRadius), + (middlePoint.y() - finalRadius), + (finalRadius + finalRadius), + (finalRadius + finalRadius)); + auto clippedRect = it.intersected(fillTarget); + texturePainter.fillRect(clippedRect.left() * TextureGenerator::m_textureSize, + clippedRect.top() * TextureGenerator::m_textureSize, + clippedRect.width() * TextureGenerator::m_textureSize, + clippedRect.height() * TextureGenerator::m_textureSize, + gradient); + break; + } + } + }; + + std::map, std::tuple> halfEdgeToTriangleMap; + for (size_t i = 0; i < m_outcome->triangles.size(); ++i) { + const auto &triangleIndices = m_outcome->triangles[i]; + if (triangleIndices.size() != 3) { + qDebug() << "Found invalid triangle indices"; + continue; + } + for (size_t j = 0; j < triangleIndices.size(); ++j) { + size_t k = (j + 1) % triangleIndices.size(); + halfEdgeToTriangleMap.insert(std::make_pair(std::make_pair(triangleIndices[j], triangleIndices[k]), + std::make_tuple(i, j, k))); + } + } + for (const auto &it: halfEdgeToTriangleMap) { + auto oppositeHalfEdge = std::make_pair(it.first.second, it.first.first); + const auto &opposite = halfEdgeToTriangleMap.find(oppositeHalfEdge); + if (opposite == halfEdgeToTriangleMap.end()) + continue; + const std::pair &source = triangleSourceNodes[std::get<0>(it.second)]; + const std::pair &oppositeSource = triangleSourceNodes[std::get<0>(opposite->second)]; + if (source.first == oppositeSource.first) + continue; + drawGradient(source.first, std::get<0>(it.second), std::get<1>(it.second), std::get<2>(it.second), oppositeSource.first); + drawGradient(oppositeSource.first, std::get<0>(opposite->second), std::get<1>(opposite->second), std::get<2>(opposite->second), source.first); + } + for (auto i = 0u; i < triangleVertexUvs.size(); i++) { QPainterPath path; const std::vector &uv = triangleVertexUvs[i]; @@ -337,12 +415,12 @@ void TextureGenerator::generate() texturePainter.drawImage(0, 0, findColorTextureResult->second); texturePainter.setClipping(false); } else { - auto findSourceNodeResult = nodeMap.find(source); - if (findSourceNodeResult != nodeMap.end() && nullptr != findSourceNodeResult->second) { - texturePainter.fillPath(path, QBrush(findSourceNodeResult->second->color)); - } else { - texturePainter.fillPath(path, QBrush(m_defaultTextureColor)); - } + //auto findSourceNodeResult = nodeMap.find(source); + //if (findSourceNodeResult != nodeMap.end() && nullptr != findSourceNodeResult->second) { + // texturePainter.fillPath(path, QBrush(findSourceNodeResult->second->color)); + //} else { + // texturePainter.fillPath(path, QBrush(m_defaultTextureColor)); + //} } // Copy normal texture if there is one auto findNormalTextureResult = m_partNormalTextureMap.find(source.first); diff --git a/thirdparty/nodemesh/nodemesh/cgalmesh.h b/thirdparty/nodemesh/nodemesh/cgalmesh.h index 5d8d4232..7a4d555a 100644 --- a/thirdparty/nodemesh/nodemesh/cgalmesh.h +++ b/thirdparty/nodemesh/nodemesh/cgalmesh.h @@ -84,5 +84,12 @@ void fetchFromCgalMesh(typename CGAL::Surface_mesh *me } } +template +bool isNullCgalMesh(typename CGAL::Surface_mesh *mesh) +{ + typename CGAL::Surface_mesh::Face_range faceRage = mesh->faces(); + return faceRage.begin() == faceRage.end(); +} + #endif diff --git a/thirdparty/nodemesh/nodemesh/combiner.cpp b/thirdparty/nodemesh/nodemesh/combiner.cpp index ced2908f..93028acf 100644 --- a/thirdparty/nodemesh/nodemesh/combiner.cpp +++ b/thirdparty/nodemesh/nodemesh/combiner.cpp @@ -47,12 +47,14 @@ Combiner::Mesh::Mesh(const std::vector &vertices, const std::vector(exactMesh)) { + delete exactMesh; + m_privateData = nullptr; + } +} + } diff --git a/thirdparty/nodemesh/nodemesh/combiner.h b/thirdparty/nodemesh/nodemesh/combiner.h index 19be4d54..36e91bcd 100644 --- a/thirdparty/nodemesh/nodemesh/combiner.h +++ b/thirdparty/nodemesh/nodemesh/combiner.h @@ -38,6 +38,8 @@ public: private: void *m_privateData = nullptr; bool m_isSelfIntersected = false; + + void validate(); }; static Mesh *combine(const Mesh &firstMesh, const Mesh &secondMesh, Method method,