Editor/Core/Validation/WeightRepair.cs
#nullable enable annotations
namespace HumanoidRigger;
using Vector3=System.Numerics.Vector3;
/// <summary>Local smoothing is accepted only when the measured stress-test objective improves.</summary>
public static class WeightRepair
{
public static ValidationReport Improve(ImportedCharacter character,GeneratedRig rig,ValidationReport initial)
=>Improve(character,rig,initial,null);
internal static ValidationReport Improve(ImportedCharacter character,GeneratedRig rig,ValidationReport initial,ValidationGeometry? geometry)
{
if(initial.Passed||initial.Issues.Any(i=>i.Error&&i.Code!="deformation"))return initial;
var roles=rig.Bones.Select(b=>b.Role).ToHashSet();
var expectedPoses=Deformation.Poses.Where(p=>Deformation.IsApplicable(p,roles)).Select(p=>p.Name).Order().ToArray();
double bestScore=Score(initial,expectedPoses);
if(!double.IsFinite(bestScore))return initial;
var adjacency=geometry?.Neighbors??character.Meshes.Select(m=>Geometry.Neighbors(m)).ToArray();float height=geometry?.Height??character.AnatomicalHeight;var best=initial;
var faces=geometry?.Faces??character.Meshes.Select(BindTriangle.Measure).ToArray();
var posed=character.Meshes.Select(m=>new Vector3[m.Vertices.Length]).ToArray();
for(int iteration=0;iteration<64;iteration++)
{
var affected=character.Meshes.Select(m=>new HashSet<int>()).ToArray();
foreach(var pose in Deformation.Poses)
{
var test=best.StressTests.FirstOrDefault(t=>t.Pose==pose.Name);
if(test is null||test.MaximumStretch<=3.8f&&test.MinimumAreaRatio>=.03f)continue;
var transforms=Deformation.BoneTransforms(rig,Deformation.JointRotations(rig,pose));
Deformation.ApplyTransforms(character,rig,transforms.Positions,transforms.Rotations,posed);
for(int part=0;part<character.Meshes.Length;part++)
{
var mesh=character.Meshes[part];var dst=posed[part];
foreach(var face in faces[part])
{
int a=face.A,b=face.B,c=face.C;
float area=face.Area;
bool bad=area>height*height*1e-10f&&Vector3.Cross(dst[b]-dst[a],dst[c]-dst[a]).Length()/area<.03f;
for(int edge=0;edge<3;edge++)
{var (v,n,length)=face.Edge(edge);if(length>height*1e-6f&&Vector3.Distance(dst[v],dst[n])/length>3.8f)bad=true;}
if(bad){affected[part].Add(a);affected[part].Add(b);affected[part].Add(c);}
}
}
}
// Each edited vertex receives a new influence array below. Sharing
// the untouched arrays avoids copying every weight on every trial.
var old=rig.Weights;var candidate=old.Select(p=>(Influence[][])p.Clone()).ToArray();
int changed=0;
for(int part=0;part<candidate.Length;part++)
{
var region=new HashSet<int>(affected[part]);foreach(var v in affected[part])foreach(var n in adjacency[part][v])region.Add(n);
foreach(var v in region)
{
if(adjacency[part][v].Count==0)continue;
var weights=new float[rig.Bones.Length];float amount=affected[part].Contains(v)?.55f:.2f;
foreach(var influence in old[part][v])weights[influence.Bone]+=influence.Weight*(1-amount);
foreach(var n in adjacency[part][v])foreach(var influence in old[part][n])weights[influence.Bone]+=amount*influence.Weight/adjacency[part][v].Count;
candidate[part][v]=Skinning.Cleanup(weights,rig.Profile.MaximumInfluences);changed++;
}
}
if(changed==0)break;
rig.Weights=candidate;ValidationReport report;
try{report=RigValidator.Validate(character,rig,faces,geometry);}
catch{rig.Weights=old;throw;}
double score=Score(report,expectedPoses);
if(score>=bestScore){rig.Weights=old;break;}
bestScore=score;
report.Repairs=best.Repairs+changed;report.RepairPasses=best.RepairPasses+1;best=report;
if(best.Passed)break;
}
return best;
}
static double Score(ValidationReport report,string[] expectedPoses)
{
// Structural errors can stop validation before any poses run. Missing,
// duplicated or non-finite measurements are not evidence of improvement.
if(!HasCompleteEvidence(report,expectedPoses))return double.PositiveInfinity;
return report.StressTests.Sum(t=>Math.Max(0,Math.Log(Math.Max(1,t.MaximumStretch/3.8)))+Math.Max(0,Math.Log(.03/Math.Max(t.MinimumAreaRatio,.00001))));
}
internal static bool HasCompleteEvidence(ValidationReport report,string[] expectedPoses)
{
return !(expectedPoses.Length==0||report.Issues.Any(i=>i.Error&&i.Code!="deformation")||
!report.StressTests.Select(t=>t.Pose).Order().SequenceEqual(expectedPoses)||
report.StressTests.Any(t=>t.NonFiniteVertices!=0||t.NonFiniteMeasurements!=0||!float.IsFinite(t.MaximumStretch)||t.MaximumStretch<1||
!float.IsFinite(t.MinimumAreaRatio)||t.MinimumAreaRatio<0||!float.IsFinite(t.SourceEdgeLength)||!float.IsFinite(t.DeformedEdgeLength)||
t.ReversedTriangles<0||!float.IsFinite(t.ReversedAreaFraction)||t.ReversedAreaFraction<0||t.ReversedAreaFraction>1));
}
}