From e60cb92218b81dd04ecec04ea2740992239f17cd Mon Sep 17 00:00:00 2001 From: ousnius Date: Sun, 9 Oct 2016 04:50:50 +0200 Subject: [PATCH] Added weight copy options and improved default values --- lang/BodySlide.pot | 19 +++--- res/xrc/Actions.xrc | 110 ++++++++++++++++++++++++++++++++++ src/components/Automorph.cpp | 66 ++++++++++---------- src/components/Automorph.h | 12 +--- src/program/OutfitProject.cpp | 6 +- src/program/OutfitProject.h | 2 +- src/program/OutfitStudio.cpp | 107 +++++++++++++++++++++++++-------- src/program/OutfitStudio.h | 7 +++ 8 files changed, 248 insertions(+), 81 deletions(-) diff --git a/lang/BodySlide.pot b/lang/BodySlide.pot index 4e582480..0543f738 100644 --- a/lang/BodySlide.pot +++ b/lang/BodySlide.pot @@ -1321,10 +1321,19 @@ msgstr "" msgid "Invert V" msgstr "" -# XRC STRINGS END HERE msgid "Toggle Zaps:" msgstr "" +msgid "Search Radius" +msgstr "" + +msgid "Max Vertex Targets" +msgstr "" + +# XRC STRINGS END HERE +msgid "Each vertex of the reference will copy its weights to the nearest collection of vertices within the given radius. Bear in mind that some geometry will always require manual tweaking to become weighted and work well. Often, the default values are sufficient." +msgstr "" + msgid "Bone information incomplete. Exported data will not contain correct bone entries! Be sure to load a reference NIF prior to export." msgstr "" @@ -1726,9 +1735,7 @@ msgstr "" msgid "Create New Slider" msgstr "" -msgid "" -"You can only edit the base shape when all sliders are zero. Do you wish to set all sliders to zero now? Note, use the pencil button next to a slider to enable editing of " -"that slider's morph." +msgid "You can only edit the base shape when all sliders are zero. Do you wish to set all sliders to zero now? Note, use the pencil button next to a slider to enable editing of that slider's morph." msgstr "" msgid "Creating project '%s'..." @@ -1791,9 +1798,7 @@ msgstr "" msgid "There are no valid shapes loaded!" msgstr "" -msgid "" -"At least one vertex does not have any weighting assigned to it. This will cause issues and you should fix it using the weight brush. The affected vertices have been put under " -"a mask. Do you want to save anyway?" +msgid "At least one vertex does not have any weighting assigned to it. This will cause issues and you should fix it using the weight brush. The affected vertices have been put under a mask. Do you want to save anyway?" msgstr "" msgid "Unweighted Vertices" diff --git a/res/xrc/Actions.xrc b/res/xrc/Actions.xrc index 429c164d..71c5fbee 100644 --- a/res/xrc/Actions.xrc +++ b/res/xrc/Actions.xrc @@ -703,4 +703,114 @@ + + + 414,169 + Copy Bone Weights + 1 + + wxVERTICAL + + + wxALL|wxEXPAND + 5 + + + 550 + + + + + wxEXPAND|wxALL + 5 + + 0 + 3 + 0 + 0 + + + + + wxLEFT|wxRIGHT + 5 + + + -1 + + + + + wxLEFT|wxRIGHT + 5 + + 5000 + 0 + 15000 + 400,-1 + + + + + wxLEFT|wxRIGHT + 5 + + 60,-1 + 5.00000 + + + + + wxLEFT|wxRIGHT + 5 + + + -1 + + + + + wxLEFT|wxRIGHT + 5 + + 4 + 0 + 12 + 400,-1 + + + + + wxLEFT|wxRIGHT + 5 + + 60,-1 + 4 + + + + + + + wxEXPAND|wxALL + 5 + + + wxALIGN_CENTER_HORIZONTAL|wxALL + 5 + + + + + + wxALIGN_CENTER_HORIZONTAL|wxALL + 5 + + + + + + + + diff --git a/src/components/Automorph.cpp b/src/components/Automorph.cpp index 91a81552..56511776 100644 --- a/src/components/Automorph.cpp +++ b/src/components/Automorph.cpp @@ -11,8 +11,6 @@ Automorph::Automorph() { refTree = nullptr; srcDiffData = nullptr; bEnableMask = true; - proximity_radius = 10.0f; - max_prox_points = 5.0f; } Automorph::~Automorph() { @@ -230,34 +228,35 @@ void Automorph::MeshFromNifShape(mesh* m, NifFile& ref, const string& shapeName) m->tris[j] = nifTris[j]; } -void Automorph::BuildProximityCache(const string &shapeName) { +void Automorph::ClearProximityCache() { + prox_cache.clear(); +} + +void Automorph::BuildProximityCache(const string &shapeName, const float& proximityRadius) { mesh* m = sourceShapes[shapeName]; - totalCount = 0; - maxCount = 0; - minCount = 60000; - proximity_radius = 10.0f; + int maxCount = 0; + int minCount = 60000; for (int i = 0; i < m->nVerts; i++) { int resultCount; if (foreignShapes.find(shapeName) != foreignShapes.end()) { Vector3 vtmp(m->verts[i].x * -10.0f, m->verts[i].z * 10.0f, m->verts[i].y * 10.0f); - resultCount = refTree->kd_nn(&vtmp, proximity_radius); + resultCount = refTree->kd_nn(&vtmp, proximityRadius); } else - resultCount = refTree->kd_nn(&m->verts[i], proximity_radius); + resultCount = refTree->kd_nn(&m->verts[i], proximityRadius); if (resultCount < minCount) minCount = resultCount; if (resultCount > maxCount) maxCount = resultCount; - totalCount += resultCount; vector indexResults; - for (int j = 0; j < resultCount; j++) - indexResults.push_back(refTree->queryResult[j] /*.vertex_index*/); + for (int id = 0; id < resultCount; id++) + indexResults.push_back(refTree->queryResult[id]); + prox_cache[i] = indexResults; } - avgCount = (float)totalCount / (float)m->nVerts; } void Automorph::GetRawResultDiff(const string& shapeName, const string& sliderName, unordered_map& outDiff) { @@ -360,7 +359,7 @@ string Automorph::ResultDataName(const string& shapeName, const string& sliderNa return f->second; } -void Automorph::GenerateResultDiff(const string& shapeName, const string &sliderName, const string& refDataName) { +void Automorph::GenerateResultDiff(const string& shapeName, const string &sliderName, const string& refDataName, const int& maxResults) { unordered_map* diffData = srcDiffData->GetDiffSet(refDataName); if (!diffData) return; @@ -375,58 +374,55 @@ void Automorph::GenerateResultDiff(const string& shapeName, const string &slider resultDiffData.AddEmptySet(shapeName + sliderName, shapeName); - ushort index = 0xFFFF; for (int i = 0; i < m->nVerts; i++) { - index++; vector* vertProx = &prox_cache[i]; int nValues = vertProx->size(); - if (nValues > 10) - nValues = 10; + if (nValues > maxResults) + nValues = maxResults; + int nearMoves = 0; - double invDistTotal = 0.0f; + double invDistTotal = 0.0; double weight; - double invDist[40]; - Vector3 effectVector[40]; Vector3 totalMove; + vector invDist(nValues); + vector effectVector(nValues); for (int j = 0; j < nValues; j++) { ushort vi = (*vertProx)[j].vertex_index; auto diffItem = diffData->find(vi); if (diffItem != diffData->end()) { weight = (*vertProx)[j].distance; // "weight" is just a placeholder here... - if (weight == 0) - invDist[nearMoves] = 1000; // Exact match, choose big nearness weight. + if (weight == 0.0) + invDist[nearMoves] = 1000.0; // Exact match, choose big nearness weight. else - invDist[nearMoves] = 1 / weight; + invDist[nearMoves] = 1.0 / weight; + invDistTotal += invDist[nearMoves]; effectVector[nearMoves] = diffItem->second; nearMoves++; } - else if (j == 0) { // Closest proximity vert has zero movement. - // nearmoves=0; - // break; + else if (j == 0) { + // Closest proximity vert has zero movement + nearMoves = 0; + break; } - } + if (nearMoves == 0) continue; - totalMove.x = 0; - totalMove.y = 0; - totalMove.z = 0; + totalMove.Zero(); for (int j = 0; j < nearMoves; j++) { weight = invDist[j] / invDistTotal; totalMove += (effectVector[j] * (float)weight); - //totalmove.x += ( weight ) * effectVector[j].x; - //totalmove.y += ( weight ) * effectVector[j].y; - //totalmove.z += ( weight ) * effectVector[j].z; } if (m->vcolors && bEnableMask) totalMove = totalMove * (1.0f - m->vcolors[i].x); + if (totalMove.DistanceTo(Vector3(0.0f, 0.0f, 0.0f)) < EPSILON) continue; - resultDiffData.UpdateDiff(shapeName + sliderName, shapeName, index, totalMove); + resultDiffData.UpdateDiff(shapeName + sliderName, shapeName, i, totalMove); } } diff --git a/src/components/Automorph.h b/src/components/Automorph.h index c24b9593..b2ed7241 100644 --- a/src/components/Automorph.h +++ b/src/components/Automorph.h @@ -24,9 +24,6 @@ class Automorph { DiffDataSets* srcDiffData; // Either __srcDiffData or an external linked data set. DiffDataSets resultDiffData; // Diffs calculated by AutoMorph. - float proximity_radius; - float max_prox_points; - bool bEnableMask; // Use red component of mesh vertex color as a mask for morphing. // A translation between shapetarget + slidername and the data name for the result diff data set. @@ -36,10 +33,6 @@ class Automorph { public: mesh* morphRef; - int totalCount; - float avgCount; - int maxCount; - int minCount; Automorph(); ~Automorph(); @@ -73,11 +66,12 @@ class Automorph { void MeshFromNifShape(mesh* m, NifFile& ref, const string& shapeName); void MeshFromObjShape(mesh* m, ObjFile& ref, const string& shapeName); - void BuildProximityCache(const string& shapeName); + void ClearProximityCache(); + void BuildProximityCache(const string& shapeName, const float& proximityRadius = 10.0f); // shapeName = name of the mesh to morph (eg "IronArmor") also known as target name. // sliderName = name of the morph to apply (eg "BreastsSH"). - void GenerateResultDiff(const string& shapeName, const string& sliderName, const string& refDataName); + void GenerateResultDiff(const string& shapeName, const string& sliderName, const string& refDataName, const int& maxResults = 10); void SetResultDataName(const string& shapeName, const string& sliderName, const string& dataName); string ResultDataName(const string& shapeName, const string& sliderName); diff --git a/src/program/OutfitProject.cpp b/src/program/OutfitProject.cpp index cb191527..7af1a948 100644 --- a/src/program/OutfitProject.cpp +++ b/src/program/OutfitProject.cpp @@ -1168,7 +1168,7 @@ void OutfitProject::RotateShape(const string& shapeName, const Vector3& angle, u workNif.RotateShape(shapeName, angle, mask); } -void OutfitProject::CopyBoneWeights(const string& destShape, unordered_map* mask, vector* inBoneList) { +void OutfitProject::CopyBoneWeights(const string& destShape, const float& proximityRadius, const int& maxResults, unordered_map* mask, vector* inBoneList) { if (baseShape.empty()) return; @@ -1208,7 +1208,7 @@ void OutfitProject::CopyBoneWeights(const string& destShape, unordered_mapsize(); int prog = 40; @@ -1216,7 +1216,7 @@ void OutfitProject::CopyBoneWeights(const string& destShape, unordered_map diffResult; morpher.GetRawResultDiff(destShape, wtSet, diffResult); diff --git a/src/program/OutfitProject.h b/src/program/OutfitProject.h index 749ee7b8..c8dd5d11 100644 --- a/src/program/OutfitProject.h +++ b/src/program/OutfitProject.h @@ -162,7 +162,7 @@ class OutfitProject { // This is done by creating several virtual sliders that contain weight offsets for each vertex per bone. // These data sets are then temporarily linked to the AutoMorph class and result 'diffs' are generated. // The resulting data is then written back to the outfit shape as the green color channel. - void CopyBoneWeights(const string& destShape, unordered_map* mask = nullptr, vector* inBoneList = nullptr); + void CopyBoneWeights(const string& destShape, const float& proximityRadius, const int& maxResults, unordered_map* mask = nullptr, vector* inBoneList = nullptr); // Transfers the weights of the selected bones from reference to chosen shape 1:1. Requires same vertex count and order. void TransferSelectedWeights(const string& destShape, unordered_map* mask = nullptr, vector* inBoneList = nullptr); bool HasUnweighted(); diff --git a/src/program/OutfitStudio.cpp b/src/program/OutfitStudio.cpp index f354fa83..77a25bb1 100644 --- a/src/program/OutfitStudio.cpp +++ b/src/program/OutfitStudio.cpp @@ -4420,6 +4420,8 @@ void OutfitStudio::OnSliderConform(wxCommandEvent& WXUNUSED(event)) { UpdateProgress(99); } + project->morpher.ClearProximityCache(); + if (statusBar) statusBar->SetStatusText(_("Shape(s) conformed.")); @@ -5254,6 +5256,49 @@ void OutfitStudio::OnDeleteBoneFromSelected(wxCommandEvent& WXUNUSED(event)) { } } +bool OutfitStudio::ShowWeightCopy(WeightCopyOptions& options) { + wxDialog dlg; + if (wxXmlResource::Get()->LoadDialog(&dlg, this, "dlgCopyWeights")) { + XRCCTRL(dlg, "proximityRadiusSlider", wxSlider)->Bind(wxEVT_SLIDER, [&dlg](wxCommandEvent&) { + float changed = XRCCTRL(dlg, "proximityRadiusSlider", wxSlider)->GetValue() / 1000.0f; + changed = min(changed, 15.0f); + changed = max(changed, 0.0f); + XRCCTRL(dlg, "proximityRadiusText", wxTextCtrl)->ChangeValue(wxString::Format("%0.5f", changed)); + }); + + XRCCTRL(dlg, "proximityRadiusText", wxTextCtrl)->Bind(wxEVT_TEXT, [&dlg](wxCommandEvent&) { + float changed = atof(XRCCTRL(dlg, "proximityRadiusText", wxTextCtrl)->GetValue().c_str()); + changed = min(changed, 15.0f); + changed = max(changed, 0.0f); + XRCCTRL(dlg, "proximityRadiusSlider", wxSlider)->SetValue(changed * 1000); + }); + + XRCCTRL(dlg, "maxResultsSlider", wxSlider)->Bind(wxEVT_SLIDER, [&dlg](wxCommandEvent&) { + int changed = XRCCTRL(dlg, "maxResultsSlider", wxSlider)->GetValue(); + changed = min(changed, 12); + changed = max(changed, 0); + XRCCTRL(dlg, "maxResultsText", wxTextCtrl)->ChangeValue(wxString::Format("%d", changed)); + }); + + XRCCTRL(dlg, "maxResultsText", wxTextCtrl)->Bind(wxEVT_TEXT, [&dlg](wxCommandEvent&) { + int changed = atol(XRCCTRL(dlg, "maxResultsText", wxTextCtrl)->GetValue().c_str()); + changed = min(changed, 12); + changed = max(changed, 0); + XRCCTRL(dlg, "maxResultsSlider", wxSlider)->SetValue(changed); + }); + + dlg.Bind(wxEVT_CHAR_HOOK, &OutfitStudio::OnEnterClose, this); + + if (dlg.ShowModal() == wxID_OK) { + options.proximityRadius = atof(XRCCTRL(dlg, "proximityRadiusText", wxTextCtrl)->GetValue().c_str()); + options.maxResults = atol(XRCCTRL(dlg, "maxResultsText", wxTextCtrl)->GetValue().c_str()); + return true; + } + } + + return false; +} + void OutfitStudio::OnCopyBoneWeight(wxCommandEvent& WXUNUSED(event)) { if (!activeItem) { wxMessageBox(_("There is no shape selected!"), _("Error")); @@ -5263,22 +5308,27 @@ void OutfitStudio::OnCopyBoneWeight(wxCommandEvent& WXUNUSED(event)) { if (project->GetBaseShape().empty()) return; - StartProgress(_("Copying bone weights...")); + WeightCopyOptions options; + if (ShowWeightCopy(options)) { + StartProgress(_("Copying bone weights...")); - unordered_map mask; - for (int i = 0; i < selectedItems.size(); i++) { - if (!project->IsBaseShape(selectedItems[i]->shapeName)) { - wxLogMessage("Copying bone weights to '%s'...", selectedItems[i]->shapeName); - mask.clear(); - glView->GetShapeMask(mask, selectedItems[i]->shapeName); - project->CopyBoneWeights(selectedItems[i]->shapeName, &mask); + unordered_map mask; + for (int i = 0; i < selectedItems.size(); i++) { + if (!project->IsBaseShape(selectedItems[i]->shapeName)) { + wxLogMessage("Copying bone weights to '%s'...", selectedItems[i]->shapeName); + mask.clear(); + glView->GetShapeMask(mask, selectedItems[i]->shapeName); + project->CopyBoneWeights(selectedItems[i]->shapeName, options.proximityRadius, options.maxResults, &mask); + } + else + wxMessageBox(_("Sorry, you can't copy weights from the reference shape to itself. Skipping this shape."), _("Can't copy weights"), wxICON_WARNING); } - else - wxMessageBox(_("Sorry, you can't copy weights from the reference shape to itself. Skipping this shape."), _("Can't copy weights"), wxICON_WARNING); - } - UpdateProgress(100, _("Finished")); - EndProgress(); + project->morpher.ClearProximityCache(); + + UpdateProgress(100, _("Finished")); + EndProgress(); + } } void OutfitStudio::OnCopySelectedWeight(wxCommandEvent& WXUNUSED(event)) { @@ -5303,22 +5353,27 @@ void OutfitStudio::OnCopySelectedWeight(wxCommandEvent& WXUNUSED(event)) { selectedBones.push_back(boneName); } - StartProgress(_("Copying selected bone weights...")); + WeightCopyOptions options; + if (ShowWeightCopy(options)) { + StartProgress(_("Copying selected bone weights...")); - unordered_map mask; - for (int i = 0; i < selectedItems.size(); i++) { - if (!project->IsBaseShape(selectedItems[i]->shapeName)) { - wxLogMessage("Copying selected bone weights to '%s' for %s...", selectedItems[i]->shapeName, bonesString); - mask.clear(); - glView->GetShapeMask(mask, selectedItems[i]->shapeName); - project->CopyBoneWeights(selectedItems[i]->shapeName, &mask, &selectedBones); + unordered_map mask; + for (int i = 0; i < selectedItems.size(); i++) { + if (!project->IsBaseShape(selectedItems[i]->shapeName)) { + wxLogMessage("Copying selected bone weights to '%s' for %s...", selectedItems[i]->shapeName, bonesString); + mask.clear(); + glView->GetShapeMask(mask, selectedItems[i]->shapeName); + project->CopyBoneWeights(selectedItems[i]->shapeName, options.proximityRadius, options.maxResults, &mask, &selectedBones); + } + else + wxMessageBox(_("Sorry, you can't copy weights from the reference shape to itself. Skipping this shape."), _("Can't copy weights"), wxICON_WARNING); } - else - wxMessageBox(_("Sorry, you can't copy weights from the reference shape to itself. Skipping this shape."), _("Can't copy weights"), wxICON_WARNING); - } - UpdateProgress(100, _("Finished")); - EndProgress(); + project->morpher.ClearProximityCache(); + + UpdateProgress(100, _("Finished")); + EndProgress(); + } } void OutfitStudio::OnTransferSelectedWeight(wxCommandEvent& WXUNUSED(event)) { diff --git a/src/program/OutfitStudio.h b/src/program/OutfitStudio.h index e9ba62af..fa5e47ab 100644 --- a/src/program/OutfitStudio.h +++ b/src/program/OutfitStudio.h @@ -83,6 +83,11 @@ class PartitionItemData : public wxTreeItemData { } }; +struct WeightCopyOptions { + float proximityRadius; + int maxResults; +}; + class OutfitStudio; @@ -770,6 +775,8 @@ class OutfitStudio : public wxFrame { void WorkingGUIFromProj(); + bool ShowWeightCopy(WeightCopyOptions& options); + void OnSashPosChanged(wxSplitterEvent& event); void OnMoveWindow(wxMoveEvent& event); void OnSetSize(wxSizeEvent& event);