diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts index 78188e31..31c7fba5 100644 --- a/languages/dust3d_zh_CN.ts +++ b/languages/dust3d_zh_CN.ts @@ -606,16 +606,16 @@ Tips: 曲线 - Accelerating: - 加速: + Accelerating + 加速 - Bouncing: - 回弹: + Decelerating + 减速 - Decelerating: - 减速: + Bouncing + 回弹 @@ -807,6 +807,10 @@ Tips: Transparency 透明度 + + Countershaded + 肚白效果 + PoseEditWidget diff --git a/src/document.cpp b/src/document.cpp index 8fee7d76..bffd13e7 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -1081,6 +1081,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId part["name"] = partIt.second.name; if (partIt.second.materialAdjusted()) part["materialId"] = partIt.second.materialId.toString(); + if (partIt.second.countershaded) + part["countershaded"] = "true"; snapshot->parts[part["id"]] = part; } for (const auto &nodeIt: nodeMap) { @@ -1386,6 +1388,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) const auto &materialIdIt = partKv.second.find("materialId"); if (materialIdIt != partKv.second.end()) part.materialId = oldNewIdMap[QUuid(materialIdIt->second)]; + part.countershaded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "countershaded")); newAddedPartIds.insert(part.id); } for (const auto &it: cutFaceLinkedIdModifyMap) { @@ -2744,6 +2747,21 @@ void Document::setPartHollowThickness(QUuid partId, float hollowThickness) emit skeletonChanged(); } +void Document::setPartCountershaded(QUuid partId, bool countershaded) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + if (part->second.countershaded == countershaded) + return; + part->second.countershaded = countershaded; + part->second.dirty = true; + emit partCountershadeStateChanged(partId); + emit textureChanged(); +} + void Document::setPartCutRotation(QUuid partId, float cutRotation) { auto part = partMap.find(partId); diff --git a/src/document.h b/src/document.h index 07c2c1d3..d9d0f4c7 100644 --- a/src/document.h +++ b/src/document.h @@ -434,6 +434,7 @@ signals: void partTargetChanged(QUuid partId); void partColorSolubilityChanged(QUuid partId); void partHollowThicknessChanged(QUuid partId); + void partCountershadeStateChanged(QUuid partId); void componentCombineModeChanged(QUuid componentId); void cleanup(); void cleanupScript(); @@ -622,6 +623,7 @@ public slots: void setPartTarget(QUuid partId, PartTarget target); void setPartColorSolubility(QUuid partId, float solubility); void setPartHollowThickness(QUuid partId, float hollowThickness); + void setPartCountershaded(QUuid partId, bool countershaded); 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 022acb04..592d88bf 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -1018,6 +1018,7 @@ DocumentWindow::DocumentWindow() : connect(m_document, &Document::partHollowThicknessChanged, partTreeWidget, &PartTreeWidget::partHollowThicknessChanged); connect(m_document, &Document::partMaterialIdChanged, partTreeWidget, &PartTreeWidget::partMaterialIdChanged); connect(m_document, &Document::partColorSolubilityChanged, partTreeWidget, &PartTreeWidget::partColorSolubilityChanged); + connect(m_document, &Document::partCountershadeStateChanged, partTreeWidget, &PartTreeWidget::partCountershadeStateChanged); 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/main.cpp b/src/main.cpp index cc1572fc..e376cb2e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,7 +40,7 @@ int main(int argc, char ** argv) darkPalette.setColor(QPalette::BrightText, Theme::red); darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); darkPalette.setColor(QPalette::Highlight, Theme::red); - darkPalette.setColor(QPalette::HighlightedText, Theme::black); + darkPalette.setColor(QPalette::HighlightedText, Theme::black); qApp->setPalette(darkPalette); //qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #fc6621; border: 1px solid white; }"); diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index 228f9a86..6ce4294f 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -587,6 +587,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt const auto &originNodeIdString = nodeIndexToIdStringMap[node.originNodeIndex]; OutcomePaintNode paintNode; + paintNode.originNodeIndex = node.originNodeIndex; paintNode.originNodeId = QUuid(originNodeIdString); paintNode.radius = node.radius; paintNode.origin = node.position; @@ -615,6 +616,8 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt paintNode.baseNormal = builder->nodeBaseNormal(i); paintNode.direction = builder->nodeTraverseDirection(i); paintNode.order = builder->nodeTraverseOrder(i); + + partCache.outcomeNodes[paintNode.originNodeIndex].direction = paintNode.direction; } bool hasMeshError = false; diff --git a/src/motiontimelinewidget.cpp b/src/motiontimelinewidget.cpp index 08029495..895334af 100644 --- a/src/motiontimelinewidget.cpp +++ b/src/motiontimelinewidget.cpp @@ -193,9 +193,20 @@ void MotionTimelineWidget::showInterpolationSettingPopup(int clipIndex, const QP QWidget *cubicWidget = new QWidget; QCheckBox *hasAcceleratingBox = new QCheckBox(); + hasAcceleratingBox->setText(tr("Accelerating")); + Theme::initCheckbox(hasAcceleratingBox); + QCheckBox *hasDeceleratingBox = new QCheckBox(); + hasDeceleratingBox->setText(tr("Decelerating")); + Theme::initCheckbox(hasDeceleratingBox); + QCheckBox *bouncingBeginBox = new QCheckBox(); + bouncingBeginBox->setText(tr("Bouncing")); + Theme::initCheckbox(bouncingBeginBox); + QCheckBox *bouncingEndBox = new QCheckBox(); + bouncingEndBox->setText(tr("Bouncing")); + Theme::initCheckbox(bouncingEndBox); QStackedWidget *stackedWidget = new QStackedWidget; stackedWidget->addWidget(linearWidget); @@ -271,29 +282,13 @@ void MotionTimelineWidget::showInterpolationSettingPopup(int clipIndex, const QP QVBoxLayout *cubicLayout = new QVBoxLayout; QHBoxLayout *acceleratingLayout = new QHBoxLayout; - { - QFormLayout *formLayout = new QFormLayout; - formLayout->addRow(tr("Accelerating:"), hasAcceleratingBox); - acceleratingLayout->addLayout(formLayout); - } - { - QFormLayout *formLayout = new QFormLayout; - formLayout->addRow(tr("Bouncing:"), bouncingBeginBox); - acceleratingLayout->addLayout(formLayout); - } + acceleratingLayout->addWidget(hasAcceleratingBox); + acceleratingLayout->addWidget(bouncingBeginBox); cubicLayout->addLayout(acceleratingLayout); QHBoxLayout *deceleratingLayout = new QHBoxLayout; - { - QFormLayout *formLayout = new QFormLayout; - formLayout->addRow(tr("Decelerating:"), hasDeceleratingBox); - deceleratingLayout->addLayout(formLayout); - } - { - QFormLayout *formLayout = new QFormLayout; - formLayout->addRow(tr("Bouncing:"), bouncingEndBox); - deceleratingLayout->addLayout(formLayout); - } + deceleratingLayout->addWidget(hasDeceleratingBox); + deceleratingLayout->addWidget(bouncingEndBox); cubicLayout->addLayout(deceleratingLayout); cubicWidget->setLayout(cubicLayout); diff --git a/src/outcome.h b/src/outcome.h index 81d15c8b..ec302af1 100644 --- a/src/outcome.h +++ b/src/outcome.h @@ -23,10 +23,12 @@ struct OutcomeNode QUuid mirrorFromPartId; QUuid mirroredByPartId; BoneMark boneMark; + QVector3D direction; }; struct OutcomePaintNode { + int originNodeIndex; QUuid originNodeId; QVector3D origin; float radius = 0; diff --git a/src/parttreewidget.cpp b/src/parttreewidget.cpp index aa6a7227..95eebcbb 100644 --- a/src/parttreewidget.cpp +++ b/src/parttreewidget.cpp @@ -1046,6 +1046,17 @@ void PartTreeWidget::partColorSolubilityChanged(QUuid partId) widget->updateColorButton(); } +void PartTreeWidget::partCountershadeStateChanged(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 e35d0b56..3e89cd56 100644 --- a/src/parttreewidget.h +++ b/src/parttreewidget.h @@ -71,6 +71,7 @@ public slots: void partHollowThicknessChanged(QUuid partId); void partMaterialIdChanged(QUuid partId); void partColorSolubilityChanged(QUuid partId); + void partCountershadeStateChanged(QUuid partId); void partChecked(QUuid partId); void partUnchecked(QUuid partId); void partComponentChecked(QUuid partId); diff --git a/src/partwidget.cpp b/src/partwidget.cpp index 2d63773b..068e4d4d 100644 --- a/src/partwidget.cpp +++ b/src/partwidget.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "partwidget.h" #include "theme.h" @@ -172,6 +173,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : connect(this, &PartWidget::setPartMaterialId, m_document, &Document::setPartMaterialId); connect(this, &PartWidget::setPartColorSolubility, m_document, &Document::setPartColorSolubility); connect(this, &PartWidget::setPartHollowThickness, m_document, &Document::setPartHollowThickness); + connect(this, &PartWidget::setPartCountershaded, m_document, &Document::setPartCountershaded); connect(this, &PartWidget::checkPart, m_document, &Document::checkPart); connect(this, &PartWidget::enableBackgroundBlur, m_document, &Document::enableBackgroundBlur); connect(this, &PartWidget::disableBackgroundBlur, m_document, &Document::disableBackgroundBlur); @@ -361,10 +363,21 @@ void PartWidget::showColorSettingPopup(const QPoint &pos) palette.setColor(QPalette::Button, choosenColor); pickButton->setPalette(palette); + QCheckBox *countershadeStateBox = new QCheckBox(); + Theme::initCheckbox(countershadeStateBox); + countershadeStateBox->setText(tr("Countershaded")); + countershadeStateBox->setChecked(part->countershaded); + + connect(countershadeStateBox, &QCheckBox::stateChanged, this, [=]() { + emit setPartCountershaded(m_partId, countershadeStateBox->isChecked()); + emit groupOperationAdded(); + }); + QHBoxLayout *colorLayout = new QHBoxLayout; colorLayout->addWidget(colorEraser); colorLayout->addWidget(pickButton); colorLayout->addStretch(); + colorLayout->addWidget(countershadeStateBox); connect(colorEraser, &QPushButton::clicked, [=]() { emit setPartColorState(m_partId, false, Qt::white); @@ -894,7 +907,7 @@ void PartWidget::updateColorButton() qDebug() << "Part not found:" << m_partId; return; } - if (part->hasColor || part->materialAdjusted() || part->colorSolubilityAdjusted()) + if (part->hasColor || part->materialAdjusted() || part->colorSolubilityAdjusted() || part->countershaded) 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 6244ebc8..53c892d6 100644 --- a/src/partwidget.h +++ b/src/partwidget.h @@ -29,6 +29,7 @@ signals: void setPartMaterialId(QUuid partId, QUuid materialId); void setPartColorSolubility(QUuid partId, float colorSolubility); void setPartHollowThickness(QUuid partId, float hollowThickness); + void setPartCountershaded(QUuid partId, bool countershaded); void movePartUp(QUuid partId); void movePartDown(QUuid partId); void movePartToTop(QUuid partId); diff --git a/src/preferenceswidget.cpp b/src/preferenceswidget.cpp index 8db258af..ef023731 100644 --- a/src/preferenceswidget.cpp +++ b/src/preferenceswidget.cpp @@ -58,11 +58,13 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent) }); QCheckBox *flatShadingBox = new QCheckBox(); + Theme::initCheckbox(flatShadingBox); connect(flatShadingBox, &QCheckBox::stateChanged, this, [=]() { Preferences::instance().setFlatShading(flatShadingBox->isChecked()); }); QCheckBox *threeNodesBranchEnabledBox = new QCheckBox(); + Theme::initCheckbox(threeNodesBranchEnabledBox); connect(threeNodesBranchEnabledBox, &QCheckBox::stateChanged, this, [=]() { Preferences::instance().setThreeNodesBranchEnableState(threeNodesBranchEnabledBox->isChecked()); }); diff --git a/src/scriptvariableswidget.cpp b/src/scriptvariableswidget.cpp index b6beffa7..17206de3 100644 --- a/src/scriptvariableswidget.cpp +++ b/src/scriptvariableswidget.cpp @@ -87,6 +87,7 @@ void ScriptVariablesWidget::reload() auto defaultValue = isTrueValueString(valueOfKeyInMapOrEmpty(variable.second, "defaultValue")); QCheckBox *checkBox = new QCheckBox; + Theme::initCheckbox(checkBox); checkBox->setText(name); checkBox->setChecked(value); diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 3f25094d..c5322c20 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -178,6 +178,7 @@ public: float deformMapScale; QUuid deformMapImageId; float hollowThickness; + bool countershaded; SkeletonPart(const QUuid &withId=QUuid()) : visible(true), locked(false), @@ -198,7 +199,8 @@ public: target(PartTarget::Model), colorSolubility(0.0), deformMapScale(1.0), - hollowThickness(0.0) + hollowThickness(0.0), + countershaded(false) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -311,6 +313,7 @@ public: materialId = other.materialId; target = other.target; colorSolubility = other.colorSolubility; + countershaded = other.countershaded; } void updatePreviewMesh(MeshLoader *previewMesh) { diff --git a/src/texturegenerator.cpp b/src/texturegenerator.cpp index 32b4a150..fb690d01 100644 --- a/src/texturegenerator.cpp +++ b/src/texturegenerator.cpp @@ -171,6 +171,8 @@ void TextureGenerator::prepare() materialId = QUuid(materialIdIt->second); QUuid partId = QUuid(partIt.first); updatedMaterialIdMap.insert({partId, materialId}); + if (isTrueValueString(valueOfKeyInMapOrEmpty(partIt.second, "countershaded"))) + m_countershadedPartIds.insert(partId); } for (const auto &bmeshNode: m_outcome->nodes) { for (size_t i = 0; i < (int)TextureType::Count - 1; ++i) { @@ -221,6 +223,7 @@ void TextureGenerator::generate() const auto &triangleVertexUvs = *m_outcome->triangleVertexUvs(); const auto &triangleSourceNodes = *m_outcome->triangleSourceNodes(); const auto &partUvRects = *m_outcome->partUvRects(); + const auto &triangleNormals = m_outcome->triangleNormals; std::map partColorMap; std::map, const OutcomeNode *> nodeMap; @@ -382,7 +385,7 @@ void TextureGenerator::generate() drawTexture(partRoughnessTexturePixmaps, textureRoughnessPainter, false); drawTexture(partAmbientOcclusionTexturePixmaps, textureAmbientOcclusionPainter, false); - auto drawGradient = [&](const QUuid &partId, size_t triangleIndex, size_t firstVertexIndex, size_t secondVertexIndex, + auto drawBySolubility = [&](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); @@ -495,8 +498,110 @@ void TextureGenerator::generate() 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); + drawBySolubility(source.first, std::get<0>(it.second), std::get<1>(it.second), std::get<2>(it.second), oppositeSource.first); + drawBySolubility(oppositeSource.first, std::get<0>(opposite->second), std::get<1>(opposite->second), std::get<2>(opposite->second), source.first); + } + + // Draw belly white + texturePainter.setCompositionMode(QPainter::CompositionMode_SoftLight); + for (size_t triangleIndex = 0; triangleIndex < m_outcome->triangles.size(); ++triangleIndex) { + const auto &normal = triangleNormals[triangleIndex]; + const std::pair &source = triangleSourceNodes[triangleIndex]; + const auto &partId = source.first; + if (m_countershadedPartIds.find(partId) == m_countershadedPartIds.end()) + continue; + + const auto &allRects = partUvRects.find(partId); + if (allRects == partUvRects.end()) { + qDebug() << "Found part uv rects failed"; + continue; + } + + const auto &findOutcomeNode = nodeMap.find(source); + if (findOutcomeNode == nodeMap.end()) + continue; + const OutcomeNode *outcomeNode = findOutcomeNode->second; + if (qAbs(QVector3D::dotProduct(outcomeNode->direction, QVector3D(0, 1, 0))) >= 0.707) { + if (QVector3D::dotProduct(normal, QVector3D(0, 0, 1)) <= 0.0) + continue; + } else { + if (QVector3D::dotProduct(normal, QVector3D(0, -1, 0)) <= 0.0) + continue; + } + + const auto &triangleIndices = m_outcome->triangles[triangleIndex]; + if (triangleIndices.size() != 3) { + qDebug() << "Found invalid triangle indices"; + continue; + } + + const std::vector &uv = triangleVertexUvs[triangleIndex]; + QVector2D middlePoint = (uv[0] + uv[1] + uv[2]) / 3.0; + float finalRadius = (uv[0].distanceToPoint(uv[1]) + + uv[1].distanceToPoint(uv[2]) + + uv[2].distanceToPoint(uv[0])) / 3.0; + QRadialGradient gradient(QPointF(middlePoint.x() * TextureGenerator::m_textureSize, + middlePoint.y() * TextureGenerator::m_textureSize), + finalRadius * TextureGenerator::m_textureSize); + gradient.setColorAt(0.0, Qt::white); + gradient.setColorAt(1.0, Qt::transparent); + for (const auto &it: allRects->second) { + if (it.contains(middlePoint.x(), middlePoint.y())) { + QRectF fillTarget((middlePoint.x() - finalRadius), + (middlePoint.y() - finalRadius), + (finalRadius + finalRadius), + (finalRadius + finalRadius)); + auto clippedRect = it.intersected(fillTarget); + QRectF translatedRect = { + clippedRect.left() * TextureGenerator::m_textureSize, + clippedRect.top() * TextureGenerator::m_textureSize, + clippedRect.width() * TextureGenerator::m_textureSize, + clippedRect.height() * TextureGenerator::m_textureSize + }; + texturePainter.fillRect(translatedRect, gradient); + } + } + + // Fill the neighbor halfedges + for (int i = 0; i < 3; ++i) { + int j = (i + 1) % 3; + auto oppositeHalfEdge = std::make_pair(triangleIndices[j], triangleIndices[i]); + const auto &opposite = halfEdgeToTriangleMap.find(oppositeHalfEdge); + if (opposite == halfEdgeToTriangleMap.end()) + continue; + auto oppositeTriangleIndex = std::get<0>(opposite->second); + const std::pair &oppositeSource = triangleSourceNodes[oppositeTriangleIndex]; + if (partId == oppositeSource.first) + continue; + const auto &oppositeAllRects = partUvRects.find(oppositeSource.first); + if (oppositeAllRects == partUvRects.end()) { + qDebug() << "Found part uv rects failed"; + continue; + } + const std::vector &oppositeUv = triangleVertexUvs[oppositeTriangleIndex]; + QVector2D oppositeMiddlePoint = (oppositeUv[std::get<1>(opposite->second)] + oppositeUv[std::get<2>(opposite->second)]) * 0.5; + QRadialGradient oppositeGradient(QPointF(oppositeMiddlePoint.x() * TextureGenerator::m_textureSize, + oppositeMiddlePoint.y() * TextureGenerator::m_textureSize), + finalRadius * TextureGenerator::m_textureSize); + oppositeGradient.setColorAt(0.0, Qt::white); + oppositeGradient.setColorAt(1.0, Qt::transparent); + for (const auto &it: oppositeAllRects->second) { + if (it.contains(oppositeMiddlePoint.x(), oppositeMiddlePoint.y())) { + QRectF fillTarget((oppositeMiddlePoint.x() - finalRadius), + (oppositeMiddlePoint.y() - finalRadius), + (finalRadius + finalRadius), + (finalRadius + finalRadius)); + auto clippedRect = it.intersected(fillTarget); + QRectF translatedRect = { + clippedRect.left() * TextureGenerator::m_textureSize, + clippedRect.top() * TextureGenerator::m_textureSize, + clippedRect.width() * TextureGenerator::m_textureSize, + clippedRect.height() * TextureGenerator::m_textureSize + }; + texturePainter.fillRect(translatedRect, oppositeGradient); + } + } + } } hasNormalMap = !m_partNormalTextureMap.empty(); diff --git a/src/texturegenerator.h b/src/texturegenerator.h index 61689798..09498b51 100644 --- a/src/texturegenerator.h +++ b/src/texturegenerator.h @@ -58,6 +58,7 @@ private: std::map> m_partMetalnessTextureMap; std::map> m_partRoughnessTextureMap; std::map> m_partAmbientOcclusionTextureMap; + std::set m_countershadedPartIds; Snapshot *m_snapshot; }; diff --git a/src/theme.cpp b/src/theme.cpp index ce8f131f..919d7193 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -177,6 +177,13 @@ void Theme::initToolButton(QPushButton *button) button->setFocusPolicy(Qt::NoFocus); } +void Theme::initCheckbox(QCheckBox *checkbox) +{ + QPalette palette = checkbox->palette(); + palette.setColor(QPalette::Background, Theme::white); + checkbox->setPalette(palette); +} + QWidget *Theme::createHorizontalLineWidget() { QWidget *hrLightWidget = new QWidget; diff --git a/src/theme.h b/src/theme.h index 49ff4733..5a049fd1 100644 --- a/src/theme.h +++ b/src/theme.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "QtAwesome.h" class Theme @@ -53,6 +54,7 @@ public: static void initAwesomeToolButtonWithoutFont(QPushButton *button); static void initAwsomeBaseSizes(); static void initToolButton(QPushButton *button); + static void initCheckbox(QCheckBox *checkbox); }; #endif