Skip to content

Commit

Permalink
Split bone weight auto-normalize code out of TweakBrush (#244)
Browse files Browse the repository at this point in the history
I extracted the code for normalizing weights automatically out of
TweakBrush.cpp into WeightNorm.{h,cpp}.  This has two benefits:
1.  There's only one copy of the code instead of three, making it
easier to maintain.
2.  The code can be re-used for other weight-modifying algorithms.
  • Loading branch information
sts1skj authored Feb 2, 2020
1 parent 3f2ca36 commit 9542527
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 233 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ set(OSsources
src/components/RefTemplates.cpp
src/components/TweakBrush.cpp
src/components/UndoHistory.cpp
src/components/WeightNorm.cpp
src/files/FBXWrangler.cpp
src/program/EditUV.cpp
src/program/FBXImportDialog.cpp
Expand Down
2 changes: 2 additions & 0 deletions OutfitStudio.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@
<ClInclude Include="src\components\TweakBrush.h" />
<ClInclude Include="src\components\UndoState.h" />
<ClInclude Include="src\components\UndoHistory.h" />
<ClInclude Include="src\components\WeightNorm.h" />
<ClInclude Include="src\files\FBXWrangler.h" />
<ClInclude Include="src\files\MaterialFile.h" />
<ClInclude Include="src\files\ObjFile.h" />
Expand Down Expand Up @@ -820,6 +821,7 @@
<ClCompile Include="src\components\SliderSet.cpp" />
<ClCompile Include="src\components\TweakBrush.cpp" />
<ClCompile Include="src\components\UndoHistory.cpp" />
<ClCompile Include="src\components\WeightNorm.cpp" />
<ClCompile Include="src\files\FBXWrangler.cpp" />
<ClCompile Include="src\files\MaterialFile.cpp" />
<ClCompile Include="src\files\ObjFile.cpp" />
Expand Down
6 changes: 6 additions & 0 deletions OutfitStudio.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,9 @@
<ClInclude Include="src\components\UndoHistory.h">
<Filter>Components</Filter>
</ClInclude>
<ClInclude Include="src\components\WeightNorm.h">
<Filter>Components</Filter>
</ClInclude>
<ClInclude Include="src\components\Automorph.h">
<Filter>Components</Filter>
</ClInclude>
Expand Down Expand Up @@ -1836,6 +1839,9 @@
<ClCompile Include="src\components\UndoHistory.cpp">
<Filter>Components</Filter>
</ClCompile>
<ClCompile Include="src\components\WeightNorm.cpp">
<Filter>Components</Filter>
</ClCompile>
<ClCompile Include="src\components\Automorph.cpp">
<Filter>Components</Filter>
</ClCompile>
Expand Down
249 changes: 16 additions & 233 deletions src/components/TweakBrush.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ See the included LICENSE file
*/

#include "TweakBrush.h"
#include "Anim.h"
#include "WeightNorm.h"

std::vector<std::future<void>> TweakStroke::normalUpdates{};

Expand Down Expand Up @@ -896,8 +896,6 @@ void TB_XForm::brushAction(mesh* m, TweakPickInfo& pickInfo, const int*, int, Un
m->QueueUpdate(mesh::UpdateType::Position);
}

constexpr double WEIGHT_EPSILON = .001;

TB_Weight::TB_Weight() :TweakBrush() {
brushType = TBT_WEIGHT;
strength = 0.0015f;
Expand All @@ -911,91 +909,19 @@ TB_Weight::~TB_Weight() {
}

void TB_Weight::brushAction(mesh* refmesh, TweakPickInfo& pickInfo, const int* points, int nPoints, UndoStateShape &uss) {
const unsigned int nBones = boneNames.size();
const unsigned int nLBones = lockedBoneNames.size();
// Create uss.boneWeights if necessary
if (uss.boneWeights.size() != nBones) {
uss.boneWeights.resize(nBones);
for (unsigned int bi = 0; bi < nBones; ++bi)
uss.boneWeights[bi].boneName = boneNames[bi];
}
// Stash weights pointers so we don't look them up over and over.
std::vector<std::unordered_map<ushort, float>*> wPtrs(nBones), lWPtrs(nLBones);
for (unsigned int bi = 0; bi < nBones; ++bi)
wPtrs[bi] = animInfo->GetWeightsPtr(refmesh->shapeName, boneNames[bi]);
for (unsigned int bi = 0; bi < nLBones; ++bi)
lWPtrs[bi] = animInfo->GetWeightsPtr(refmesh->shapeName, lockedBoneNames[bi]);
// Fill in uss start and end values for vertices that don't have them yet
for (int pi = 0; pi < nPoints; pi++) {
int i = points[pi];
for (unsigned int bi = 0; bi < nBones; ++bi) {
auto &bw = uss.boneWeights[bi].weights;
if (bw.find(i) == bw.end()) {
float val = 0.0;
if (wPtrs[bi] && wPtrs[bi]->find(i) != wPtrs[bi]->end())
val = (*wPtrs[bi])[i];
if (val > 0.0 || bi == 0)
bw[i].startVal = bw[i].endVal = val;
}
}
}

BoneWeightAutoNormalizer nzer;
nzer.SetUp(&uss, animInfo, refmesh->shapeName, boneNames, lockedBoneNames, bSpreadWeight);
nzer.GrabStartingWeights(points, nPoints);
for (int pi = 0; pi < nPoints; pi++) {
int i = points[pi];
// Calculate total locked and normalizable weight
float totW = 0.0;
for (unsigned int bi = 1; bi < nBones; ++bi) {
auto &bw = uss.boneWeights[bi].weights;
if (bw.find(i) != bw.end())
totW += bw[i].endVal;
}
float totLW = 0.0;
for (unsigned int bi = 0; bi < nLBones; ++bi) {
if (!lWPtrs[bi]) continue;
auto wpit = lWPtrs[bi]->find(i);
if (wpit != lWPtrs[bi]->end())
totLW += wpit->second;
}
// Calculate available weight
if (totLW < WEIGHT_EPSILON) totLW = 0.0;
float availW = 1.0 - totLW;
if (availW < WEIGHT_EPSILON) availW = 0.0;
// Calculate result
float sw = uss.boneWeights[0].weights[i].endVal;
float ew = bFixedWeight ? strength * 10.0f - sw : strength;
ew *= getFalloff(pickInfo.origin.DistanceTo(refmesh->verts[i]));
ew *= 1.0f - refmesh->vcolors[i].x;
float fw = sw + ew;
if (fw < WEIGHT_EPSILON) fw = 0.0;
if (fw > availW) fw = availW;
if (fw - 1.0 > -WEIGHT_EPSILON) fw = 1.0;
uss.boneWeights[0].weights[i].endVal = refmesh->vcolors[i].y = fw;
// Normalize
float redFac = totW <= WEIGHT_EPSILON ? 0.0 : (availW - fw) / totW;
totW = 0.0;
for (unsigned int bi = 1; bi < nBones; ++bi) {
auto owi = uss.boneWeights[bi].weights.find(i);
if (owi == uss.boneWeights[bi].weights.end()) continue;
float &ow = owi->second.endVal;
ow *= redFac;
if (ow < WEIGHT_EPSILON) ow = 0.0;
if (ow - 1.0 > -WEIGHT_EPSILON) ow = 1.0;
totW += ow;
}
// Check if normalization didn't work; if so, split the missing
// weight among the normalize bones.
if (1.0 - totW - fw - totLW > WEIGHT_EPSILON && nBones >= 2 && bSpreadWeight) {
float remainW = (1.0 - totW - fw - totLW) / (nBones - 1);
for (unsigned int bi = 1; bi < nBones; ++bi) {
auto &bw = uss.boneWeights[bi].weights;
auto owi = bw.find(i);
if (owi == bw.end())
bw[i].startVal = bw[i].endVal = 0.0;
bw[i].endVal += remainW;
}
}
fw = nzer.SetWeight(0, i, fw);
refmesh->vcolors[i].y = fw;
}

refmesh->QueueUpdate(mesh::UpdateType::VertexColors);
}

Expand All @@ -1011,91 +937,19 @@ TB_Unweight::~TB_Unweight() {
}

void TB_Unweight::brushAction(mesh* refmesh, TweakPickInfo& pickInfo, const int* points, int nPoints, UndoStateShape &uss) {
const unsigned int nBones = boneNames.size();
const unsigned int nLBones = lockedBoneNames.size();
// Create uss.boneWeights if necessary
if (uss.boneWeights.size() != nBones) {
uss.boneWeights.resize(nBones);
for (unsigned int bi = 0; bi < nBones; ++bi)
uss.boneWeights[bi].boneName = boneNames[bi];
}
// Stash weights pointers so we don't look them up over and over.
std::vector<std::unordered_map<ushort, float>*> wPtrs(nBones), lWPtrs(nLBones);
for (unsigned int bi = 0; bi < nBones; ++bi)
wPtrs[bi] = animInfo->GetWeightsPtr(refmesh->shapeName, boneNames[bi]);
for (unsigned int bi = 0; bi < nLBones; ++bi)
lWPtrs[bi] = animInfo->GetWeightsPtr(refmesh->shapeName, lockedBoneNames[bi]);
// Fill in uss start and end values for vertices that don't have them yet
BoneWeightAutoNormalizer nzer;
nzer.SetUp(&uss, animInfo, refmesh->shapeName, boneNames, lockedBoneNames, bSpreadWeight);
nzer.GrabStartingWeights(points, nPoints);
for (int pi = 0; pi < nPoints; pi++) {
int i = points[pi];
for (unsigned int bi = 0; bi < nBones; ++bi) {
auto &bw = uss.boneWeights[bi].weights;
if (bw.find(i) == bw.end()) {
float val = 0.0;
if (wPtrs[bi] && wPtrs[bi]->find(i) != wPtrs[bi]->end())
val = (*wPtrs[bi])[i];
if (val > 0.0 || bi == 0)
bw[i].startVal = bw[i].endVal = val;
}
}
}

for (int pi = 0; pi < nPoints; pi++) {
int i = points[pi];
// Calculate total locked and normalizable weight
float totW = 0.0;
for (unsigned int bi = 1; bi < nBones; ++bi) {
auto &bw = uss.boneWeights[bi].weights;
if (bw.find(i) != bw.end())
totW += bw[i].endVal;
}
float totLW = 0.0;
for (unsigned int bi = 0; bi < nLBones; ++bi) {
if (!lWPtrs[bi]) continue;
auto wpit = lWPtrs[bi]->find(i);
if (wpit != lWPtrs[bi]->end())
totLW += wpit->second;
}
// Calculate available weight
if (totLW < WEIGHT_EPSILON) totLW = 0.0;
float availW = 1.0 - totLW;
if (availW < WEIGHT_EPSILON) availW = 0.0;
// Calculate result
float sw = uss.boneWeights[0].weights[i].endVal;
float ew = strength;
ew *= getFalloff(pickInfo.origin.DistanceTo(refmesh->verts[i]));
ew *= 1.0f - refmesh->vcolors[i].x;
float fw = sw + ew;
if (fw < WEIGHT_EPSILON) fw = 0.0;
if (fw > availW) fw = availW;
if (fw - 1.0 > -WEIGHT_EPSILON) fw = 1.0;
uss.boneWeights[0].weights[i].endVal = refmesh->vcolors[i].y = fw;
// Normalize
float redFac = totW <= WEIGHT_EPSILON ? 0.0 : (availW - fw) / totW;
totW = 0.0;
for (unsigned int bi = 1; bi < nBones; ++bi) {
auto owi = uss.boneWeights[bi].weights.find(i);
if (owi == uss.boneWeights[bi].weights.end()) continue;
float &ow = owi->second.endVal;
ow *= redFac;
if (ow < WEIGHT_EPSILON) ow = 0.0;
if (ow - 1.0 > -WEIGHT_EPSILON) ow = 1.0;
totW += ow;
}
// Check if normalization didn't work; if so, split the missing
// weight among the normalize bones.
if (1.0 - totW - fw - totLW > WEIGHT_EPSILON && nBones >= 2 && bSpreadWeight) {
float remainW = (1.0 - totW - fw - totLW) / (nBones - 1);
for (unsigned int bi = 1; bi < nBones; ++bi) {
auto &bw = uss.boneWeights[bi].weights;
auto owi = bw.find(i);
if (owi == bw.end())
bw[i].startVal = bw[i].endVal = 0.0;
bw[i].endVal += remainW;
}
}
fw = nzer.SetWeight(0, i, fw);
refmesh->vcolors[i].y = fw;
}

refmesh->QueueUpdate(mesh::UpdateType::VertexColors);
}

Expand Down Expand Up @@ -1181,34 +1035,9 @@ void TB_SmoothWeight::hclapFilter(mesh* refmesh, const int* points, int nPoints,
}

void TB_SmoothWeight::brushAction(mesh* refmesh, TweakPickInfo& pickInfo, const int* points, int nPoints, UndoStateShape &uss) {
const unsigned int nBones = boneNames.size();
const unsigned int nLBones = lockedBoneNames.size();
// Create uss.boneWeights if necessary
if (uss.boneWeights.size() != nBones) {
uss.boneWeights.resize(nBones);
for (unsigned int bi = 0; bi < nBones; ++bi)
uss.boneWeights[bi].boneName = boneNames[bi];
}
// Stash weights pointers so we don't look them up over and over.
std::vector<std::unordered_map<ushort, float>*> wPtrs(nBones), lWPtrs(nLBones);
for (unsigned int bi = 0; bi < nBones; ++bi)
wPtrs[bi] = animInfo->GetWeightsPtr(refmesh->shapeName, boneNames[bi]);
for (unsigned int bi = 0; bi < nLBones; ++bi)
lWPtrs[bi] = animInfo->GetWeightsPtr(refmesh->shapeName, lockedBoneNames[bi]);
// Fill in uss start and end values for vertices that don't have them yet
for (int pi = 0; pi < nPoints; pi++) {
int i = points[pi];
for (unsigned int bi = 0; bi < nBones; ++bi) {
auto &bw = uss.boneWeights[bi].weights;
if (bw.find(i) == bw.end()) {
float val = 0.0;
if (wPtrs[bi] && wPtrs[bi]->find(i) != wPtrs[bi]->end())
val = (*wPtrs[bi])[i];
if (val > 0.0 || bi == 0)
bw[i].startVal = bw[i].endVal = val;
}
}
}
BoneWeightAutoNormalizer nzer;
nzer.SetUp(&uss, animInfo, refmesh->shapeName, boneNames, lockedBoneNames, bSpreadWeight);
nzer.GrabStartingWeights(points, nPoints);

// Copy previous iteration's results into wv
std::unordered_map<int, float> wv;
Expand All @@ -1224,60 +1053,14 @@ void TB_SmoothWeight::brushAction(mesh* refmesh, TweakPickInfo& pickInfo, const

for (int pi = 0; pi < nPoints; pi++) {
int i = points[pi];
// Calculate total locked and normalizable weight
float totW = 0.0;
for (unsigned int bi = 1; bi < nBones; ++bi) {
auto &bw = uss.boneWeights[bi].weights;
if (bw.find(i) != bw.end())
totW += bw[i].endVal;
}
float totLW = 0.0;
for (unsigned int bi = 0; bi < nLBones; ++bi) {
if (!lWPtrs[bi]) continue;
auto wpit = lWPtrs[bi]->find(i);
if (wpit != lWPtrs[bi]->end())
totLW += wpit->second;
}
// Calculate available weight
if (totLW < WEIGHT_EPSILON) totLW = 0.0;
float availW = 1.0 - totLW;
if (availW < WEIGHT_EPSILON) availW = 0.0;
// Calculate result
float sw = uss.boneWeights[0].weights[i].endVal;
float ew = wv[i] - sw;
ew *= getFalloff(pickInfo.origin.DistanceTo(refmesh->verts[i]));
ew *= 1.0f - refmesh->vcolors[i].x;
float fw = sw + ew;
if (fw < WEIGHT_EPSILON) fw = 0.0;
if (fw - 1.0 > -WEIGHT_EPSILON) fw = 1.0;
if (fw > availW) fw = availW;
uss.boneWeights[0].weights[i].endVal = refmesh->vcolors[i].y = fw;
// Normalize
float redFac = totW <= WEIGHT_EPSILON ? 0.0 : (availW - fw) / totW;
totW = 0.0;
for (unsigned int bi = 1; bi < nBones; ++bi) {
auto owi = uss.boneWeights[bi].weights.find(i);
if (owi == uss.boneWeights[bi].weights.end()) continue;
float &ow = owi->second.endVal;
ow *= redFac;
if (ow < WEIGHT_EPSILON) ow = 0.0;
if (ow - 1.0 > -WEIGHT_EPSILON) ow = 1.0;
totW += ow;
}
// Check if normalization didn't work; if so, split the missing
// weight among the normalize bones.
if (1.0 - totW - fw - totLW > WEIGHT_EPSILON && nBones >= 2 && bSpreadWeight) {
float remainW = (1.0 - totW - fw - totLW) / (nBones - 1);
for (unsigned int bi = 1; bi < nBones; ++bi) {
auto &bw = uss.boneWeights[bi].weights;
auto owi = bw.find(i);
if (owi == bw.end())
bw[i].startVal = bw[i].endVal = 0.0;
bw[i].endVal += remainW;
}
}
fw = nzer.SetWeight(0, i, fw);
refmesh->vcolors[i].y = fw;
}

refmesh->QueueUpdate(mesh::UpdateType::VertexColors);
}

Expand Down
Loading

0 comments on commit 9542527

Please sign in to comment.