1649 lines
63 KiB
C#
1649 lines
63 KiB
C#
// Warning: Some assembly references could not be resolved automatically. This might lead to incorrect decompilation of some parts,
|
|
// for ex. property getter/setter access. To get optimal decompilation results, please manually add the missing references to the list of loaded assemblies.
|
|
// EdenAutoMorpherScript, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
|
// Eden.AutoMorpher.WeightTransferUtil
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.Collections;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
public class WeightTransferUtil
|
|
{
|
|
[Serializable]
|
|
public class Settings
|
|
{
|
|
[Header("최근접 표면 매칭")]
|
|
public float maxDistance = 0.1f;
|
|
|
|
public float maxNormalAngleDeg = 35f;
|
|
|
|
public bool allowFlippedNormal = true;
|
|
|
|
[Header("추가 스무딩")]
|
|
public bool enableSmoothing = true;
|
|
|
|
public int smoothingIterations = 4;
|
|
|
|
[Range(0f, 1f)]
|
|
public float smoothingAlpha = 0.25f;
|
|
|
|
[Header("본 수 제한 & 임계값")]
|
|
public bool enforceFourBoneLimit = true;
|
|
|
|
public float tinyWeight = 0.0001f;
|
|
|
|
public bool weightInClothes;
|
|
}
|
|
|
|
private bool NameContainsAny(string lowerName, string[] patterns)
|
|
{
|
|
for (int i = 0; i < patterns.Length; i++)
|
|
{
|
|
if (lowerName.Contains(patterns[i]))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool IsBreastOrButtBone(Transform t)
|
|
{
|
|
if (t == null)
|
|
{
|
|
return false;
|
|
}
|
|
string[] patterns = new string[6] { "breast", "boob", "bust", "oppai", "chichi", "mune" };
|
|
string[] patterns2 = new string[6] { "butt", "buttock", "buttox", "glute", "glutes", "gluteus" };
|
|
string lowerName = t.name.ToLowerInvariant();
|
|
if (this.NameContainsAny(lowerName, patterns))
|
|
{
|
|
return true;
|
|
}
|
|
if (this.NameContainsAny(lowerName, patterns2))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public IEnumerator RetargetAndTransfer(ClothInstance cloth, Animator avatarAnimator, IReadOnlyList<SkinnedMeshRenderer> bodyMeshes, IReadOnlyList<Mesh> bodyBakedMeshes, int referenceBodyIndex = 0, Settings settings = null, Dictionary<Transform, ClothBoneType> boneTypeMap = null, Dictionary<Transform, Transform> bodyToClothesMap = null)
|
|
{
|
|
if (cloth == null || cloth.smr == null || cloth.editableMesh == null)
|
|
{
|
|
throw new AutoMorpherException("Cloth Instance is Invalid", "[WeightTransferUtil] RetargetAndTransfer\n - cloth is null or cloth.smr is null or cloth.editableMesh is null");
|
|
}
|
|
if (cloth.worldVertices == null || cloth.worldVertices.Length == 0)
|
|
{
|
|
throw new AutoMorpherException("Cloth World Vertices are Missing", "[WeightTransferUtil] RetargetAndTransfer\n - cloth.worldVertices is null or empty");
|
|
}
|
|
if (cloth.bakedMesh == null)
|
|
{
|
|
throw new AutoMorpherException("Cloth Baked Mesh is Missing", "[WeightTransferUtil] RetargetAndTransfer\n - cloth.bakedMesh is null");
|
|
}
|
|
if (avatarAnimator == null || !avatarAnimator.isHuman)
|
|
{
|
|
throw new AutoMorpherException("Avatar Animator is Invalid", "[WeightTransferUtil] RetargetAndTransfer\n - avatarAnimator is null or not a Humanoid");
|
|
}
|
|
if (bodyMeshes == null || bodyMeshes.Count == 0)
|
|
{
|
|
throw new AutoMorpherException("Body Meshes are Missing", "[WeightTransferUtil] RetargetAndTransfer\n - bodyMeshes is null or empty");
|
|
}
|
|
if (referenceBodyIndex < 0 || referenceBodyIndex >= bodyMeshes.Count)
|
|
{
|
|
referenceBodyIndex = 0;
|
|
}
|
|
SkinnedMeshRenderer bodyRefSmr = bodyMeshes[referenceBodyIndex];
|
|
if (bodyRefSmr == null || bodyRefSmr.sharedMesh == null)
|
|
{
|
|
throw new AutoMorpherException("Reference Body Mesh is Invalid", "[WeightTransferUtil] RetargetAndTransfer\n - bodyRefSmr is null or bodyRefSmr.sharedMesh is null\n - referenceBodyIndex : " + referenceBodyIndex);
|
|
}
|
|
if (settings == null)
|
|
{
|
|
settings = new Settings();
|
|
}
|
|
Transform[] accessoryBones = null;
|
|
float[,] accessoryWeights = null;
|
|
BoneWeight[] prevBoneWeights = null;
|
|
yield return null;
|
|
Transform[] bones;
|
|
Dictionary<Transform, int> accIndexByTransform;
|
|
float tiny;
|
|
if (boneTypeMap != null && cloth.smr != null && cloth.editableMesh != null)
|
|
{
|
|
bones = cloth.smr.bones;
|
|
if (bones != null && bones.Length != 0)
|
|
{
|
|
HashSet<Transform> value = null;
|
|
if (cloth.humanoidMatchedBones != null)
|
|
{
|
|
cloth.humanoidMatchedBones.TryGetValue(HumanBodyBones.Head, out value);
|
|
}
|
|
HashSet<Transform> hashSet = new HashSet<Transform>();
|
|
foreach (Transform transform in bones)
|
|
{
|
|
if (!(transform == null) && boneTypeMap.TryGetValue(transform, out var value2) && value2 == ClothBoneType.Accessory)
|
|
{
|
|
hashSet.Add(transform);
|
|
}
|
|
}
|
|
if (value != null)
|
|
{
|
|
foreach (Transform item in value)
|
|
{
|
|
if (!(item == null) && Array.IndexOf(bones, item) >= 0)
|
|
{
|
|
hashSet.Add(item);
|
|
}
|
|
}
|
|
}
|
|
List<Transform> list = new List<Transform>();
|
|
accIndexByTransform = new Dictionary<Transform, int>();
|
|
foreach (Transform transform2 in bones)
|
|
{
|
|
if (!(transform2 == null) && hashSet.Contains(transform2) && !this.IsBreastOrButtBone(transform2) && !accIndexByTransform.ContainsKey(transform2))
|
|
{
|
|
accIndexByTransform[transform2] = list.Count;
|
|
list.Add(transform2);
|
|
}
|
|
}
|
|
if (list.Count > 0)
|
|
{
|
|
accessoryBones = list.ToArray();
|
|
int vertexCount = cloth.editableMesh.vertexCount;
|
|
accessoryWeights = new float[vertexCount, accessoryBones.Length];
|
|
BoneWeight[] boneWeights = cloth.editableMesh.boneWeights;
|
|
if (boneWeights != null && boneWeights.Length == vertexCount)
|
|
{
|
|
tiny = 1E-06f;
|
|
for (int k = 0; k < vertexCount; k++)
|
|
{
|
|
BoneWeight boneWeight = boneWeights[k];
|
|
Acc(boneWeight.boneIndex0, boneWeight.weight0, k);
|
|
Acc(boneWeight.boneIndex1, boneWeight.weight1, k);
|
|
Acc(boneWeight.boneIndex2, boneWeight.weight2, k);
|
|
Acc(boneWeight.boneIndex3, boneWeight.weight3, k);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
int bodyBoneCount = bodyRefSmr.bones.Length;
|
|
yield return null;
|
|
this.RetargetClothToAvatarSkeleton(cloth, avatarAnimator, bodyRefSmr, accessoryBones);
|
|
this.TransferFromBodyToCloth(avatarAnimator, cloth, bodyMeshes, bodyBakedMeshes, settings, accessoryBones, accessoryWeights, bodyBoneCount, prevBoneWeights);
|
|
if (settings.weightInClothes)
|
|
{
|
|
this.MoveBodyWeightsToClothesBones(cloth.smr, cloth.smr.sharedMesh, bodyToClothesMap, accessoryBones);
|
|
}
|
|
void Acc(int boneIndex, float w, int v)
|
|
{
|
|
if (!(w <= tiny) && boneIndex >= 0 && boneIndex < bones.Length)
|
|
{
|
|
Transform transform3 = bones[boneIndex];
|
|
if (!(transform3 == null) && accIndexByTransform.TryGetValue(transform3, out var value3))
|
|
{
|
|
accessoryWeights[v, value3] += w;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RetargetClothToAvatarSkeleton(ClothInstance cloth, Animator avatarAnimator, SkinnedMeshRenderer bodyRefSmr, Transform[] accessoryBones = null)
|
|
{
|
|
SkinnedMeshRenderer smr = cloth.smr;
|
|
Mesh sharedMesh = cloth.smr.sharedMesh;
|
|
if (sharedMesh == null)
|
|
{
|
|
throw new AutoMorpherException("Cloth Mesh is Missing", "[WeightTransferUtil] RetargetClothToAvatarSkeleton\n - clothMesh is null");
|
|
}
|
|
Transform[] bones = smr.bones;
|
|
Transform[] bones2 = bodyRefSmr.bones;
|
|
if (bones == null || bones.Length == 0 || bones2 == null || bones2.Length == 0)
|
|
{
|
|
throw new AutoMorpherException("Cloth or Target Bones are Missing", "[WeightTransferUtil] RetargetClothToAvatarSkeleton\n - clothBones is null/empty or targetBones is null/empty\n - clothBones.Length : " + ((bones == null) ? (-1) : bones.Length) + "\n - targetBones.Length : " + ((bones2 == null) ? (-1) : bones2.Length));
|
|
}
|
|
Matrix4x4 localToWorldMatrix = smr.transform.localToWorldMatrix;
|
|
if (accessoryBones == null || accessoryBones.Length == 0)
|
|
{
|
|
Dictionary<Transform, HumanBodyBones> dictionary = new Dictionary<Transform, HumanBodyBones>();
|
|
foreach (KeyValuePair<HumanBodyBones, HashSet<Transform>> humanoidMatchedBone in cloth.humanoidMatchedBones)
|
|
{
|
|
HumanBodyBones key = humanoidMatchedBone.Key;
|
|
HashSet<Transform> value = humanoidMatchedBone.Value;
|
|
if (value == null)
|
|
{
|
|
continue;
|
|
}
|
|
foreach (Transform item in value)
|
|
{
|
|
if (!(item == null) && !dictionary.ContainsKey(item))
|
|
{
|
|
dictionary.Add(item, key);
|
|
}
|
|
}
|
|
}
|
|
Dictionary<HumanBodyBones, int> dictionary2 = new Dictionary<HumanBodyBones, int>();
|
|
Dictionary<Transform, int> dictionary3 = new Dictionary<Transform, int>();
|
|
for (int i = 0; i < bones2.Length; i++)
|
|
{
|
|
Transform transform = bones2[i];
|
|
if (transform != null && !dictionary3.ContainsKey(transform))
|
|
{
|
|
dictionary3.Add(transform, i);
|
|
}
|
|
}
|
|
foreach (HumanBodyBones value4 in Enum.GetValues(typeof(HumanBodyBones)))
|
|
{
|
|
if (value4 != HumanBodyBones.LastBone)
|
|
{
|
|
Transform boneTransform = avatarAnimator.GetBoneTransform(value4);
|
|
if (!(boneTransform == null) && dictionary3.TryGetValue(boneTransform, out var value2))
|
|
{
|
|
dictionary2[value4] = value2;
|
|
}
|
|
}
|
|
}
|
|
BoneWeight[] boneWeights = sharedMesh.boneWeights;
|
|
if (boneWeights == null || boneWeights.Length == 0)
|
|
{
|
|
Debug.LogWarning("[WeightTransferUtil] clothMesh.boneWeights 가 비어 있음. 리타겟할 weight 가 없음.");
|
|
return;
|
|
}
|
|
int vertexCount = sharedMesh.vertexCount;
|
|
int num = bones2.Length;
|
|
float[,] array = new float[vertexCount, num];
|
|
for (int j = 0; j < vertexCount; j++)
|
|
{
|
|
BoneWeight boneWeight = boneWeights[j];
|
|
this.AccumulateRetargetWeight(array, j, boneWeight.boneIndex0, boneWeight.weight0, bones, dictionary, dictionary2);
|
|
this.AccumulateRetargetWeight(array, j, boneWeight.boneIndex1, boneWeight.weight1, bones, dictionary, dictionary2);
|
|
this.AccumulateRetargetWeight(array, j, boneWeight.boneIndex2, boneWeight.weight2, bones, dictionary, dictionary2);
|
|
this.AccumulateRetargetWeight(array, j, boneWeight.boneIndex3, boneWeight.weight3, bones, dictionary, dictionary2);
|
|
}
|
|
BoneWeight[] boneWeights2 = this.CollapseWeightsToBoneWeights(array, 4, 0.0001f);
|
|
Matrix4x4[] array2 = new Matrix4x4[bones2.Length];
|
|
for (int k = 0; k < bones2.Length; k++)
|
|
{
|
|
Transform transform2 = bones2[k];
|
|
if (transform2 != null)
|
|
{
|
|
array2[k] = transform2.worldToLocalMatrix * localToWorldMatrix;
|
|
}
|
|
else
|
|
{
|
|
array2[k] = Matrix4x4.identity;
|
|
}
|
|
}
|
|
sharedMesh.boneWeights = boneWeights2;
|
|
sharedMesh.bindposes = array2;
|
|
smr.rootBone = bodyRefSmr.rootBone;
|
|
smr.bones = bones2;
|
|
EditorUtility.SetDirty(sharedMesh);
|
|
EditorUtility.SetDirty(smr);
|
|
return;
|
|
}
|
|
Transform[] bones3 = bodyRefSmr.bones;
|
|
if (bones3 == null || bones3.Length == 0)
|
|
{
|
|
throw new AutoMorpherException("Target Body Bones are Missing", "[WeightTransferUtil] RetargetClothToAvatarSkeleton\n - bodyRefSmr.bones is null or empty");
|
|
}
|
|
Transform[] bones4 = smr.bones;
|
|
Matrix4x4[] bindposes = sharedMesh.bindposes;
|
|
Dictionary<Transform, int> dictionary4 = new Dictionary<Transform, int>();
|
|
if (bones4 != null)
|
|
{
|
|
for (int l = 0; l < bones4.Length; l++)
|
|
{
|
|
Transform transform3 = bones4[l];
|
|
if (!(transform3 == null) && !dictionary4.ContainsKey(transform3))
|
|
{
|
|
dictionary4.Add(transform3, l);
|
|
}
|
|
}
|
|
}
|
|
int num2 = bones3.Length;
|
|
int num3 = accessoryBones.Length;
|
|
Transform[] array3 = new Transform[num2 + num3];
|
|
for (int m = 0; m < num2; m++)
|
|
{
|
|
array3[m] = bones3[m];
|
|
}
|
|
for (int n = 0; n < num3; n++)
|
|
{
|
|
array3[num2 + n] = accessoryBones[n];
|
|
}
|
|
Matrix4x4[] array4 = new Matrix4x4[array3.Length];
|
|
for (int num4 = 0; num4 < num2; num4++)
|
|
{
|
|
Transform transform4 = bones3[num4];
|
|
if (transform4 != null)
|
|
{
|
|
array4[num4] = transform4.worldToLocalMatrix * localToWorldMatrix;
|
|
}
|
|
else
|
|
{
|
|
array4[num4] = Matrix4x4.identity;
|
|
}
|
|
}
|
|
for (int num5 = 0; num5 < num3; num5++)
|
|
{
|
|
Transform transform5 = accessoryBones[num5];
|
|
Matrix4x4 matrix4x = Matrix4x4.identity;
|
|
if (transform5 != null && dictionary4.TryGetValue(transform5, out var value3))
|
|
{
|
|
matrix4x = ((bindposes == null || value3 < 0 || value3 >= bindposes.Length) ? (transform5.worldToLocalMatrix * localToWorldMatrix) : bindposes[value3]);
|
|
}
|
|
array4[num2 + num5] = matrix4x;
|
|
}
|
|
sharedMesh.bindposes = array4;
|
|
sharedMesh.boneWeights = new BoneWeight[sharedMesh.vertexCount];
|
|
smr.rootBone = bodyRefSmr.rootBone;
|
|
smr.bones = array3;
|
|
EditorUtility.SetDirty(sharedMesh);
|
|
EditorUtility.SetDirty(smr);
|
|
}
|
|
|
|
private void AccumulateRetargetWeight(float[,] W, int vertIndex, int clothBoneIndex, float weight, Transform[] clothBones, Dictionary<Transform, HumanBodyBones> clothBoneToHuman, Dictionary<HumanBodyBones, int> humanToTargetBoneIndex)
|
|
{
|
|
if (!(weight <= 0f) && clothBoneIndex >= 0 && clothBoneIndex < clothBones.Length)
|
|
{
|
|
Transform transform = clothBones[clothBoneIndex];
|
|
if (!(transform == null) && clothBoneToHuman.TryGetValue(transform, out var value) && humanToTargetBoneIndex.TryGetValue(value, out var value2))
|
|
{
|
|
W[vertIndex, value2] += weight;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void TransferFromBodyToCloth(Animator avatarAnimator, ClothInstance cloth, IReadOnlyList<SkinnedMeshRenderer> bodyMeshes, IReadOnlyList<Mesh> bodyBakedMeshes, Settings settings, Transform[] accessoryBones, float[,] accessoryWeights, int bodyBoneCount, BoneWeight[] prevBoneWeights = null)
|
|
{
|
|
Vector3[] worldVertices = cloth.worldVertices;
|
|
if (worldVertices == null || worldVertices.Length == 0)
|
|
{
|
|
throw new AutoMorpherException("Cloth World Vertices are Missing", "[WeightTransferUtil] TransferFromBodyToCloth\n - cloth.worldVertices is null or empty");
|
|
}
|
|
int[] triangles = cloth.editableMesh.triangles;
|
|
Vector3[] clothWorldNormals = this.GetClothWorldNormals(cloth);
|
|
if (clothWorldNormals == null || clothWorldNormals.Length != worldVertices.Length)
|
|
{
|
|
throw new AutoMorpherException("Cloth World Normals Calculation Failed", "[WeightTransferUtil] TransferFromBodyToCloth\n - targetNormalsWorld is null or length mismatch\n - targetVertsWorld.Length : " + ((worldVertices == null) ? (-1) : worldVertices.Length) + "\n - targetNormalsWorld.Length : " + ((clothWorldNormals == null) ? (-1) : clothWorldNormals.Length));
|
|
}
|
|
Transform[] bones = cloth.smr.bones;
|
|
int num = bones.Length;
|
|
Dictionary<Transform, int> dictionary = new Dictionary<Transform, int>();
|
|
for (int i = 0; i < bones.Length; i++)
|
|
{
|
|
Transform transform = bones[i];
|
|
if (transform != null && !dictionary.ContainsKey(transform))
|
|
{
|
|
dictionary.Add(transform, i);
|
|
}
|
|
}
|
|
int num2 = 0;
|
|
int num3 = 0;
|
|
foreach (SkinnedMeshRenderer bodyMesh in bodyMeshes)
|
|
{
|
|
if (!(bodyMesh == null) && !(bodyMesh.sharedMesh == null))
|
|
{
|
|
num2 += bodyMesh.sharedMesh.vertexCount;
|
|
num3 += bodyMesh.sharedMesh.triangles.Length;
|
|
}
|
|
}
|
|
if (num2 == 0 || num3 == 0)
|
|
{
|
|
throw new AutoMorpherException("Valid Body Mesh is Missing", "[WeightTransferUtil] TransferFromBodyToCloth\n - bodyMeshes has no valid mesh (totalVerts == 0 or totalTris == 0)");
|
|
}
|
|
Vector3[] array = new Vector3[num2];
|
|
Vector3[] array2 = new Vector3[num2];
|
|
int[] array3 = new int[num3];
|
|
float[,] W = new float[num2, num];
|
|
int num4 = 0;
|
|
int num5 = 0;
|
|
for (int j = 0; j < bodyMeshes.Count; j++)
|
|
{
|
|
SkinnedMeshRenderer skinnedMeshRenderer = bodyMeshes[j];
|
|
if (skinnedMeshRenderer == null || skinnedMeshRenderer.sharedMesh == null)
|
|
{
|
|
continue;
|
|
}
|
|
Mesh sharedMesh = skinnedMeshRenderer.sharedMesh;
|
|
Mesh mesh = bodyBakedMeshes[j];
|
|
Vector3[] vertices = mesh.vertices;
|
|
Vector3[] normals = mesh.normals;
|
|
int[] triangles2 = mesh.triangles;
|
|
int vertexCount = mesh.vertexCount;
|
|
Vector3 lossyScale = skinnedMeshRenderer.transform.lossyScale;
|
|
Vector3 vector = new Vector3(1f / Mathf.Max(lossyScale.x, 1E-08f), 1f / Mathf.Max(lossyScale.y, 1E-08f), 1f / Mathf.Max(lossyScale.z, 1E-08f));
|
|
Matrix4x4 matrix4x = skinnedMeshRenderer.transform.localToWorldMatrix * Matrix4x4.Scale(vector);
|
|
for (int k = 0; k < vertexCount; k++)
|
|
{
|
|
array[num4 + k] = matrix4x.MultiplyPoint3x4(vertices[k]);
|
|
Vector3 normalized = matrix4x.MultiplyVector(normals[k]).normalized;
|
|
array2[num4 + k] = normalized;
|
|
}
|
|
for (int l = 0; l < triangles2.Length; l++)
|
|
{
|
|
array3[num5 + l] = triangles2[l] + num4;
|
|
}
|
|
BoneWeight[] boneWeights = sharedMesh.boneWeights;
|
|
Transform[] bones2 = skinnedMeshRenderer.bones;
|
|
if (boneWeights != null && boneWeights.Length == vertexCount && bones2 != null)
|
|
{
|
|
for (int m = 0; m < vertexCount; m++)
|
|
{
|
|
BoneWeight boneWeight = boneWeights[m];
|
|
this.AccumulateBodyWeight(ref W, num4 + m, boneWeight.boneIndex0, boneWeight.weight0, bones2, dictionary);
|
|
this.AccumulateBodyWeight(ref W, num4 + m, boneWeight.boneIndex1, boneWeight.weight1, bones2, dictionary);
|
|
this.AccumulateBodyWeight(ref W, num4 + m, boneWeight.boneIndex2, boneWeight.weight2, bones2, dictionary);
|
|
this.AccumulateBodyWeight(ref W, num4 + m, boneWeight.boneIndex3, boneWeight.weight3, bones2, dictionary);
|
|
}
|
|
}
|
|
num4 += vertexCount;
|
|
num5 += triangles2.Length;
|
|
}
|
|
List<int>[] array4 = cloth.vertexAdjacency;
|
|
if (array4 == null || array4.Length != worldVertices.Length)
|
|
{
|
|
array4 = this.BuildAdjacencyFromTriangles(worldVertices.Length, triangles);
|
|
}
|
|
float[,] weights = this.FindMatchesClosestSurface(array, array3, array2, worldVertices, clothWorldNormals, W, settings.maxDistance * settings.maxDistance, settings.maxNormalAngleDeg, settings.allowFlippedNormal, out var matchedMask);
|
|
weights = this.InpaintApprox(worldVertices, triangles, weights, matchedMask, array4, 20, 0.7f);
|
|
if (settings.enableSmoothing && settings.smoothingIterations > 0 && settings.smoothingAlpha > 0f)
|
|
{
|
|
weights = this.SmoothWeightsApprox(worldVertices, weights, matchedMask, array4, settings.smoothingIterations, settings.smoothingAlpha, settings.maxDistance);
|
|
}
|
|
this.ApplyLegVertexConstraints(avatarAnimator, cloth, weights, bodyBoneCount, settings.tinyWeight);
|
|
int vertexCount2 = cloth.editableMesh.vertexCount;
|
|
num = cloth.smr.bones.Length;
|
|
if (accessoryBones != null && accessoryBones.Length != 0 && accessoryWeights != null && bodyBoneCount > 0)
|
|
{
|
|
HashSet<Transform> value = null;
|
|
if (cloth.humanoidMatchedBones != null)
|
|
{
|
|
cloth.humanoidMatchedBones.TryGetValue(HumanBodyBones.Head, out value);
|
|
}
|
|
Transform transform2 = null;
|
|
if (avatarAnimator != null)
|
|
{
|
|
transform2 = avatarAnimator.GetBoneTransform(HumanBodyBones.Head);
|
|
}
|
|
if (value != null && transform2 != null)
|
|
{
|
|
int num6 = Array.IndexOf(cloth.smr.bones, transform2);
|
|
if (num6 >= 0 && num6 < bodyBoneCount)
|
|
{
|
|
foreach (Transform item in value)
|
|
{
|
|
if (item == null)
|
|
{
|
|
continue;
|
|
}
|
|
int num7 = Array.IndexOf(accessoryBones, item);
|
|
if (num7 < 0)
|
|
{
|
|
continue;
|
|
}
|
|
for (int n = 0; n < vertexCount2; n++)
|
|
{
|
|
float num8 = accessoryWeights[n, num7];
|
|
if (!(num8 <= settings.tinyWeight))
|
|
{
|
|
weights[n, num6] += num8;
|
|
accessoryWeights[n, num7] = 0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BoneWeight[] array5;
|
|
if (accessoryBones != null && accessoryBones.Length != 0 && accessoryWeights != null && bodyBoneCount > 0 && bodyBoneCount + accessoryBones.Length == num)
|
|
{
|
|
array5 = this.MergeBodyAndAccessoryWeights(weights, accessoryWeights, bodyBoneCount, settings.tinyWeight, settings.enforceFourBoneLimit);
|
|
}
|
|
else
|
|
{
|
|
if (settings.enforceFourBoneLimit)
|
|
{
|
|
this.EnforceMaxBonesPerVertex(weights, 4, settings.tinyWeight);
|
|
}
|
|
else
|
|
{
|
|
this.ZeroSmallWeights(weights, settings.tinyWeight);
|
|
}
|
|
this.NormalizeWeightsPerVertex(weights, settings.tinyWeight);
|
|
array5 = this.CollapseWeightsToBoneWeights(weights, 4, settings.tinyWeight);
|
|
}
|
|
Mesh sharedMesh2 = cloth.smr.sharedMesh;
|
|
if (sharedMesh2.vertexCount != array5.Length)
|
|
{
|
|
throw new AutoMorpherException("Vertex Count and Weight Length Mismatch", "[WeightTransferUtil] TransferFromBodyToCloth\n - mesh.vertexCount : " + sharedMesh2.vertexCount + "\n - finalWeights.Length : " + array5.Length);
|
|
}
|
|
this.EnsureNonZeroAndNormalizeFinalWeights(cloth, array5, array4, settings.tinyWeight);
|
|
this.ApplyEquivalentVertexWeights(cloth, array5);
|
|
this.NormalizeAllBoneWeightsInPlace(array5, settings.tinyWeight);
|
|
sharedMesh2.boneWeights = array5;
|
|
cloth.smr.sharedMesh = sharedMesh2;
|
|
if (!this.TryApplyBoneWeightsWithValidation(sharedMesh2, array5, 1E-10f, out var zeroWeightVertexCount))
|
|
{
|
|
Debug.LogWarning($"[WeightValidation] Zero-weight vertices detected: {zeroWeightVertexCount}");
|
|
}
|
|
}
|
|
|
|
public void MoveBodyWeightsToClothesBones(SkinnedMeshRenderer clothesSmr, Mesh clothesMesh, Dictionary<Transform, Transform> bodyToClothesMap, Transform[] accessoryBones)
|
|
{
|
|
if (clothesSmr == null)
|
|
{
|
|
throw new AutoMorpherException("Clothes SMR is Missing", "[WeightTransferUtil] MoveBodyWeightsToClothesBones\n - clothesSmr is null");
|
|
}
|
|
if (clothesMesh == null)
|
|
{
|
|
throw new AutoMorpherException("Clothes Mesh is Missing", "[WeightTransferUtil] MoveBodyWeightsToClothesBones\n - clothesMesh is null");
|
|
}
|
|
if (bodyToClothesMap == null)
|
|
{
|
|
throw new AutoMorpherException("Body To Clothes Map is Missing", "[WeightTransferUtil] MoveBodyWeightsToClothesBones\n - bodyToClothesMap is null");
|
|
}
|
|
Transform clothesFallbackRoot = ((clothesSmr.rootBone != null) ? clothesSmr.rootBone : clothesSmr.transform);
|
|
HashSet<Transform> hashSet = null;
|
|
if (accessoryBones != null && accessoryBones.Length != 0)
|
|
{
|
|
hashSet = new HashSet<Transform>(accessoryBones);
|
|
}
|
|
Transform[] bones = clothesSmr.bones;
|
|
if (bones == null || bones.Length == 0)
|
|
{
|
|
throw new AutoMorpherException("Clothes Bones are Missing", "[WeightTransferUtil] MoveBodyWeightsToClothesBones\n - clothesSmr.bones is null or empty");
|
|
}
|
|
Transform[] array = new Transform[bones.Length];
|
|
for (int i = 0; i < bones.Length; i++)
|
|
{
|
|
Transform transform = bones[i];
|
|
if (transform == null)
|
|
{
|
|
array[i] = clothesFallbackRoot;
|
|
continue;
|
|
}
|
|
if (hashSet != null && hashSet.Contains(transform))
|
|
{
|
|
array[i] = transform;
|
|
continue;
|
|
}
|
|
Transform transform2 = EnsureClothesBoneExists(transform);
|
|
array[i] = ((transform2 != null) ? transform2 : clothesFallbackRoot);
|
|
}
|
|
clothesSmr.bones = array;
|
|
if (clothesSmr.rootBone != null && (hashSet == null || !hashSet.Contains(clothesSmr.rootBone)))
|
|
{
|
|
Transform transform3 = EnsureClothesBoneExists(clothesSmr.rootBone);
|
|
if (transform3 != null)
|
|
{
|
|
clothesSmr.rootBone = transform3;
|
|
}
|
|
}
|
|
Transform transform4 = ((clothesSmr.rootBone != null) ? clothesSmr.rootBone : clothesSmr.transform);
|
|
Matrix4x4[] array2 = new Matrix4x4[array.Length];
|
|
for (int j = 0; j < array.Length; j++)
|
|
{
|
|
Transform transform5 = array[j];
|
|
if (transform5 == null)
|
|
{
|
|
throw new AutoMorpherException("Remapped Bone is Null", "[WeightTransferUtil] MoveBodyWeightsToClothesBones\n - remappedBones contains null bone after fallback");
|
|
}
|
|
array2[j] = transform5.worldToLocalMatrix * transform4.localToWorldMatrix;
|
|
}
|
|
clothesMesh.bindposes = array2;
|
|
Transform EnsureClothesBoneExists(Transform bodyBone)
|
|
{
|
|
if (bodyBone == null)
|
|
{
|
|
return null;
|
|
}
|
|
if (bodyToClothesMap.TryGetValue(bodyBone, out var value) && value != null)
|
|
{
|
|
return value;
|
|
}
|
|
Transform transform6 = null;
|
|
if (bodyBone.parent != null)
|
|
{
|
|
transform6 = EnsureClothesBoneExists(bodyBone.parent);
|
|
}
|
|
if (transform6 == null)
|
|
{
|
|
transform6 = clothesFallbackRoot;
|
|
}
|
|
Transform transform7 = new GameObject(bodyBone.name).transform;
|
|
transform7.SetParent(transform6, worldPositionStays: false);
|
|
transform7.localPosition = bodyBone.localPosition;
|
|
transform7.localRotation = bodyBone.localRotation;
|
|
transform7.localScale = bodyBone.localScale;
|
|
bodyToClothesMap[bodyBone] = transform7;
|
|
return transform7;
|
|
}
|
|
}
|
|
|
|
private bool TryApplyBoneWeightsWithValidation(Mesh mesh, BoneWeight[] weights, float tiny, out int zeroWeightVertexCount)
|
|
{
|
|
bool result = true;
|
|
zeroWeightVertexCount = 0;
|
|
int num = weights.Length;
|
|
NativeArray<byte> bonesPerVertex = new NativeArray<byte>(num, Allocator.Temp);
|
|
List<BoneWeight1> list = new List<BoneWeight1>(num * 4);
|
|
NativeArray<BoneWeight1> weights2 = default(NativeArray<BoneWeight1>);
|
|
try
|
|
{
|
|
for (int i = 0; i < num; i++)
|
|
{
|
|
BoneWeight boneWeight = weights[i];
|
|
int count = 0;
|
|
this.AddIfValid(list, boneWeight.boneIndex0, boneWeight.weight0, tiny, ref count);
|
|
this.AddIfValid(list, boneWeight.boneIndex1, boneWeight.weight1, tiny, ref count);
|
|
this.AddIfValid(list, boneWeight.boneIndex2, boneWeight.weight2, tiny, ref count);
|
|
this.AddIfValid(list, boneWeight.boneIndex3, boneWeight.weight3, tiny, ref count);
|
|
if (count == 0)
|
|
{
|
|
zeroWeightVertexCount++;
|
|
}
|
|
bonesPerVertex[i] = (byte)count;
|
|
}
|
|
if (zeroWeightVertexCount > 0)
|
|
{
|
|
return false;
|
|
}
|
|
weights2 = new NativeArray<BoneWeight1>(list.Count, Allocator.Temp);
|
|
for (int j = 0; j < list.Count; j++)
|
|
{
|
|
weights2[j] = list[j];
|
|
}
|
|
mesh.SetBoneWeights(bonesPerVertex, weights2);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning("[SetBoneWeights] Failed: " + ex.Message);
|
|
result = false;
|
|
}
|
|
finally
|
|
{
|
|
if (weights2.IsCreated)
|
|
{
|
|
weights2.Dispose();
|
|
}
|
|
if (bonesPerVertex.IsCreated)
|
|
{
|
|
bonesPerVertex.Dispose();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private void AddIfValid(List<BoneWeight1> list, int boneIndex, float weight, float tiny, ref int count)
|
|
{
|
|
if (boneIndex >= 0 && !(weight <= tiny))
|
|
{
|
|
list.Add(new BoneWeight1
|
|
{
|
|
boneIndex = boneIndex,
|
|
weight = weight
|
|
});
|
|
count++;
|
|
}
|
|
}
|
|
|
|
private void ApplyEquivalentVertexWeights(ClothInstance cloth, BoneWeight[] finalWeights)
|
|
{
|
|
if (cloth == null || cloth.equivalentVertices == null || cloth.equivalentVertices.Count == 0 || finalWeights == null || finalWeights.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
List<List<int>> equivalentVertices = cloth.equivalentVertices;
|
|
int num = finalWeights.Length;
|
|
bool[] array = new bool[num];
|
|
foreach (List<int> item in equivalentVertices)
|
|
{
|
|
if (item == null || item.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
List<int> list = null;
|
|
bool flag = false;
|
|
for (int i = 0; i < item.Count; i++)
|
|
{
|
|
int num2 = item[i];
|
|
if (num2 >= 0 && num2 < num)
|
|
{
|
|
if (list == null)
|
|
{
|
|
list = new List<int>();
|
|
}
|
|
list.Add(num2);
|
|
if (!array[num2])
|
|
{
|
|
flag = true;
|
|
}
|
|
}
|
|
}
|
|
if (list == null || !flag)
|
|
{
|
|
continue;
|
|
}
|
|
Dictionary<int, float> weightMap = new Dictionary<int, float>();
|
|
foreach (int item2 in list)
|
|
{
|
|
BoneWeight boneWeight = finalWeights[item2];
|
|
Accumulate(boneWeight.boneIndex0, boneWeight.weight0);
|
|
Accumulate(boneWeight.boneIndex1, boneWeight.weight1);
|
|
Accumulate(boneWeight.boneIndex2, boneWeight.weight2);
|
|
Accumulate(boneWeight.boneIndex3, boneWeight.weight3);
|
|
}
|
|
if (weightMap.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
List<KeyValuePair<int, float>> list2 = weightMap.OrderByDescending((KeyValuePair<int, float> kv) => kv.Value).Take(4).ToList();
|
|
float num3 = 0f;
|
|
for (int num4 = 0; num4 < Math.Min(4, list2.Count); num4++)
|
|
{
|
|
num3 += list2[num4].Value;
|
|
}
|
|
if (num3 <= 1E-08f)
|
|
{
|
|
continue;
|
|
}
|
|
BoneWeight boneWeight2 = default(BoneWeight);
|
|
for (int num5 = 0; num5 < Math.Min(4, list2.Count); num5++)
|
|
{
|
|
int key = list2[num5].Key;
|
|
float num6 = list2[num5].Value / num3;
|
|
switch (num5)
|
|
{
|
|
case 0:
|
|
boneWeight2.boneIndex0 = key;
|
|
boneWeight2.weight0 = num6;
|
|
break;
|
|
case 1:
|
|
boneWeight2.boneIndex1 = key;
|
|
boneWeight2.weight1 = num6;
|
|
break;
|
|
case 2:
|
|
boneWeight2.boneIndex2 = key;
|
|
boneWeight2.weight2 = num6;
|
|
break;
|
|
case 3:
|
|
boneWeight2.boneIndex3 = key;
|
|
boneWeight2.weight3 = num6;
|
|
break;
|
|
}
|
|
}
|
|
foreach (int item3 in list)
|
|
{
|
|
finalWeights[item3] = boneWeight2;
|
|
array[item3] = true;
|
|
}
|
|
void Accumulate(int boneIndex, float w)
|
|
{
|
|
if (!(w <= 0f) && boneIndex >= 0)
|
|
{
|
|
if (weightMap.TryGetValue(boneIndex, out var value))
|
|
{
|
|
weightMap[boneIndex] = value + w;
|
|
}
|
|
else
|
|
{
|
|
weightMap[boneIndex] = w;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyLegVertexConstraints(Animator avatarAnimator, ClothInstance cloth, float[,] weights, int bodyBoneCount, float tinyWeight)
|
|
{
|
|
if (cloth == null || cloth.smr == null)
|
|
{
|
|
return;
|
|
}
|
|
bool[] isLeftLegVertex = cloth.isLeftLegVertex;
|
|
bool[] isRightLegVertex = cloth.isRightLegVertex;
|
|
if (isLeftLegVertex == null && isRightLegVertex == null)
|
|
{
|
|
return;
|
|
}
|
|
Transform[] bones = cloth.smr.bones;
|
|
if (bones == null)
|
|
{
|
|
return;
|
|
}
|
|
int length = weights.GetLength(0);
|
|
int length2 = weights.GetLength(1);
|
|
Transform boneTransform = avatarAnimator.GetBoneTransform(HumanBodyBones.LeftUpperLeg);
|
|
Transform boneTransform2 = avatarAnimator.GetBoneTransform(HumanBodyBones.RightUpperLeg);
|
|
bool[] array = new bool[length2];
|
|
bool[] array2 = new bool[length2];
|
|
int num = Mathf.Min(bodyBoneCount, bones.Length);
|
|
for (int i = 0; i < num; i++)
|
|
{
|
|
Transform transform = bones[i];
|
|
if (!(transform == null))
|
|
{
|
|
if (transform.IsChildOf(boneTransform))
|
|
{
|
|
array[i] = true;
|
|
}
|
|
else if (transform.IsChildOf(boneTransform2))
|
|
{
|
|
array2[i] = true;
|
|
}
|
|
}
|
|
}
|
|
for (int j = 0; j < length; j++)
|
|
{
|
|
bool flag = isLeftLegVertex != null && j < isLeftLegVertex.Length && isLeftLegVertex[j];
|
|
bool flag2 = isRightLegVertex != null && j < isRightLegVertex.Length && isRightLegVertex[j];
|
|
if (!flag && !flag2)
|
|
{
|
|
continue;
|
|
}
|
|
for (int k = 0; k < length2; k++)
|
|
{
|
|
if (weights[j, k] <= tinyWeight)
|
|
{
|
|
continue;
|
|
}
|
|
if (flag)
|
|
{
|
|
if (array2[k])
|
|
{
|
|
weights[j, k] = 0f;
|
|
}
|
|
}
|
|
else if (flag2 && array[k])
|
|
{
|
|
weights[j, k] = 0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private Vector3[] GetClothWorldNormals(ClothInstance cloth)
|
|
{
|
|
if (cloth.worldVertices == null)
|
|
{
|
|
return null;
|
|
}
|
|
int num = cloth.worldVertices.Length;
|
|
if (cloth.bakedMesh == null || cloth.bakedMesh.vertexCount != num)
|
|
{
|
|
throw new AutoMorpherException("Cloth Baked Mesh is Invalid", "[WeightTransferUtil] GetClothWorldNormals\n - bakedMesh is null or vertexCount mismatch\n - bakedMesh.vertexCount : " + ((cloth.bakedMesh == null) ? (-1) : cloth.bakedMesh.vertexCount) + "\n - worldVertices.Length : " + num);
|
|
}
|
|
Vector3[] array = new Vector3[num];
|
|
Mesh bakedMesh = cloth.bakedMesh;
|
|
Vector3[] normals = bakedMesh.normals;
|
|
if (normals == null || normals.Length != num)
|
|
{
|
|
bakedMesh.RecalculateNormals();
|
|
normals = bakedMesh.normals;
|
|
}
|
|
for (int i = 0; i < num; i++)
|
|
{
|
|
Vector3 normalized = cloth.worldNoScale.MultiplyVector(normals[i]).normalized;
|
|
array[i] = normalized;
|
|
}
|
|
return array;
|
|
}
|
|
|
|
private void AccumulateBodyWeight(ref float[,] W, int vIndex, int localBoneIndex, float weight, Transform[] bodyBones, Dictionary<Transform, int> targetBoneIndexByTransform)
|
|
{
|
|
if (!(weight <= 0f) && bodyBones != null && localBoneIndex >= 0 && localBoneIndex < bodyBones.Length)
|
|
{
|
|
Transform transform = bodyBones[localBoneIndex];
|
|
if (!(transform == null) && targetBoneIndexByTransform.TryGetValue(transform, out var value))
|
|
{
|
|
W[vIndex, value] += weight;
|
|
}
|
|
}
|
|
}
|
|
|
|
private List<int>[] BuildAdjacencyFromTriangles(int vertexCount, int[] triangles)
|
|
{
|
|
List<int>[] array = new List<int>[vertexCount];
|
|
for (int i = 0; i < vertexCount; i++)
|
|
{
|
|
array[i] = new List<int>();
|
|
}
|
|
int num = triangles.Length / 3;
|
|
for (int j = 0; j < num; j++)
|
|
{
|
|
int num2 = triangles[j * 3];
|
|
int num3 = triangles[j * 3 + 1];
|
|
int num4 = triangles[j * 3 + 2];
|
|
this.AddEdge(array, num2, num3);
|
|
this.AddEdge(array, num3, num4);
|
|
this.AddEdge(array, num4, num2);
|
|
}
|
|
return array;
|
|
}
|
|
|
|
private void AddEdge(List<int>[] adj, int a, int b)
|
|
{
|
|
if (!adj[a].Contains(b))
|
|
{
|
|
adj[a].Add(b);
|
|
}
|
|
if (!adj[b].Contains(a))
|
|
{
|
|
adj[b].Add(a);
|
|
}
|
|
}
|
|
|
|
private float[,] FindMatchesClosestSurface(Vector3[] sourceVerts, int[] sourceTris, Vector3[] sourceNormals, Vector3[] targetVerts, Vector3[] targetNormals, float[,] sourceWeights, float distanceThresholdSqr, float angleThresholdDeg, bool flipVertexNormal, out bool[] matchedMask)
|
|
{
|
|
int num = targetVerts.Length;
|
|
int length = sourceWeights.GetLength(1);
|
|
float[,] array = new float[num, length];
|
|
matchedMask = new bool[num];
|
|
int num2 = sourceTris.Length / 3;
|
|
float num3 = angleThresholdDeg * (MathF.PI / 180f);
|
|
for (int i = 0; i < num; i++)
|
|
{
|
|
Vector3 vector = targetVerts[i];
|
|
float num4 = float.PositiveInfinity;
|
|
int num5 = -1;
|
|
Vector3 vector2 = Vector3.zero;
|
|
for (int j = 0; j < num2; j++)
|
|
{
|
|
int num6 = sourceTris[j * 3];
|
|
int num7 = sourceTris[j * 3 + 1];
|
|
int num8 = sourceTris[j * 3 + 2];
|
|
Vector3 a = sourceVerts[num6];
|
|
Vector3 b = sourceVerts[num7];
|
|
Vector3 c = sourceVerts[num8];
|
|
Vector3 bary;
|
|
Vector3 vector3 = this.ClosestPointOnTriangle(vector, a, b, c, out bary);
|
|
float sqrMagnitude = (vector - vector3).sqrMagnitude;
|
|
if (sqrMagnitude < num4)
|
|
{
|
|
num4 = sqrMagnitude;
|
|
num5 = j;
|
|
vector2 = bary;
|
|
}
|
|
}
|
|
if (num5 < 0)
|
|
{
|
|
matchedMask[i] = false;
|
|
continue;
|
|
}
|
|
bool num9 = num4 <= distanceThresholdSqr;
|
|
int num10 = sourceTris[num5 * 3];
|
|
int num11 = sourceTris[num5 * 3 + 1];
|
|
int num12 = sourceTris[num5 * 3 + 2];
|
|
Vector3 vector4 = sourceNormals[num10];
|
|
Vector3 vector5 = sourceNormals[num11];
|
|
Vector3 vector6 = sourceNormals[num12];
|
|
Vector3 normalized = (vector4 * vector2.x + vector5 * vector2.y + vector6 * vector2.z).normalized;
|
|
Vector3 normalized2 = targetNormals[i].normalized;
|
|
float num13 = Mathf.Acos(Mathf.Clamp(Vector3.Dot(normalized, normalized2), -1f, 1f));
|
|
bool flag = num13 <= num3;
|
|
if (flipVertexNormal && MathF.PI - num13 <= num3)
|
|
{
|
|
flag = true;
|
|
}
|
|
bool flag2 = num9 && flag;
|
|
matchedMask[i] = flag2;
|
|
for (int k = 0; k < length; k++)
|
|
{
|
|
float num14 = sourceWeights[num10, k];
|
|
float num15 = sourceWeights[num11, k];
|
|
float num16 = sourceWeights[num12, k];
|
|
array[i, k] = num14 * vector2.x + num15 * vector2.y + num16 * vector2.z;
|
|
}
|
|
}
|
|
return array;
|
|
}
|
|
|
|
private Vector3 ClosestPointOnTriangle(Vector3 p, Vector3 a, Vector3 b, Vector3 c, out Vector3 bary)
|
|
{
|
|
Vector3 vector = b - a;
|
|
Vector3 vector2 = c - a;
|
|
Vector3 rhs = p - a;
|
|
float num = Vector3.Dot(vector, rhs);
|
|
float num2 = Vector3.Dot(vector2, rhs);
|
|
if (num <= 0f && num2 <= 0f)
|
|
{
|
|
bary = new Vector3(1f, 0f, 0f);
|
|
return a;
|
|
}
|
|
Vector3 rhs2 = p - b;
|
|
float num3 = Vector3.Dot(vector, rhs2);
|
|
float num4 = Vector3.Dot(vector2, rhs2);
|
|
if (num3 >= 0f && num4 <= num3)
|
|
{
|
|
bary = new Vector3(0f, 1f, 0f);
|
|
return b;
|
|
}
|
|
float num5 = num * num4 - num3 * num2;
|
|
if (num5 <= 0f && num >= 0f && num3 <= 0f)
|
|
{
|
|
float num6 = num / (num - num3);
|
|
bary = new Vector3(1f - num6, num6, 0f);
|
|
return a + vector * num6;
|
|
}
|
|
Vector3 rhs3 = p - c;
|
|
float num7 = Vector3.Dot(vector, rhs3);
|
|
float num8 = Vector3.Dot(vector2, rhs3);
|
|
if (num8 >= 0f && num7 <= num8)
|
|
{
|
|
bary = new Vector3(0f, 0f, 1f);
|
|
return c;
|
|
}
|
|
float num9 = num7 * num2 - num * num8;
|
|
if (num9 <= 0f && num2 >= 0f && num8 <= 0f)
|
|
{
|
|
float num10 = num2 / (num2 - num8);
|
|
bary = new Vector3(1f - num10, 0f, num10);
|
|
return a + vector2 * num10;
|
|
}
|
|
float num11 = num3 * num8 - num7 * num4;
|
|
if (num11 <= 0f && num4 - num3 >= 0f && num7 - num8 >= 0f)
|
|
{
|
|
float num12 = (num4 - num3) / (num4 - num3 + (num7 - num8));
|
|
bary = new Vector3(0f, 1f - num12, num12);
|
|
return b + (c - b) * num12;
|
|
}
|
|
float num13 = 1f / (num11 + num9 + num5);
|
|
float num14 = num9 * num13;
|
|
float num15 = num5 * num13;
|
|
float num16 = 1f - num14 - num15;
|
|
bary = new Vector3(num16, num14, num15);
|
|
return a * num16 + b * num14 + c * num15;
|
|
}
|
|
|
|
private void ZeroSmallWeights(float[,] weights, float tiny)
|
|
{
|
|
int length = weights.GetLength(0);
|
|
int length2 = weights.GetLength(1);
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
for (int j = 0; j < length2; j++)
|
|
{
|
|
if (weights[i, j] <= tiny)
|
|
{
|
|
weights[i, j] = 0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void EnforceMaxBonesPerVertex(float[,] weights, int maxBonesPerVertex, float tinyWeight)
|
|
{
|
|
int length = weights.GetLength(0);
|
|
int length2 = weights.GetLength(1);
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
List<(int, float)> list = new List<(int, float)>();
|
|
for (int j = 0; j < length2; j++)
|
|
{
|
|
float num = weights[i, j];
|
|
if (num > tinyWeight)
|
|
{
|
|
list.Add((j, num));
|
|
}
|
|
}
|
|
list.Sort(((int bone, float w) a, (int bone, float w) b) => b.w.CompareTo(a.w));
|
|
HashSet<int> hashSet = new HashSet<int>();
|
|
int num2 = Mathf.Min(maxBonesPerVertex, list.Count);
|
|
for (int num3 = 0; num3 < num2; num3++)
|
|
{
|
|
hashSet.Add(list[num3].Item1);
|
|
}
|
|
for (int num4 = 0; num4 < length2; num4++)
|
|
{
|
|
if (!hashSet.Contains(num4) || weights[i, num4] <= tinyWeight)
|
|
{
|
|
weights[i, num4] = 0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void NormalizeWeightsPerVertex(float[,] weights, float tiny)
|
|
{
|
|
int length = weights.GetLength(0);
|
|
int length2 = weights.GetLength(1);
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
float num = 0f;
|
|
for (int j = 0; j < length2; j++)
|
|
{
|
|
num += weights[i, j];
|
|
}
|
|
if (!(num < tiny))
|
|
{
|
|
float num2 = 1f / num;
|
|
for (int k = 0; k < length2; k++)
|
|
{
|
|
weights[i, k] *= num2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private BoneWeight[] CollapseWeightsToBoneWeights(float[,] weights, int maxBonesPerVert, float tiny)
|
|
{
|
|
int length = weights.GetLength(0);
|
|
int length2 = weights.GetLength(1);
|
|
BoneWeight[] array = new BoneWeight[length];
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
List<(int, float)> list = new List<(int, float)>();
|
|
for (int j = 0; j < length2; j++)
|
|
{
|
|
float num = weights[i, j];
|
|
if (num > tiny)
|
|
{
|
|
list.Add((j, num));
|
|
}
|
|
}
|
|
if (list.Count == 0)
|
|
{
|
|
array[i] = default(BoneWeight);
|
|
continue;
|
|
}
|
|
list.Sort(((int idx, float w) a, (int idx, float w) b) => b.w.CompareTo(a.w));
|
|
int num2 = Mathf.Min(maxBonesPerVert, list.Count);
|
|
float num3 = 0f;
|
|
for (int num4 = 0; num4 < num2; num4++)
|
|
{
|
|
num3 += list[num4].Item2;
|
|
}
|
|
if (num3 < tiny)
|
|
{
|
|
num3 = 1f;
|
|
}
|
|
float num5 = 1f / num3;
|
|
BoneWeight boneWeight = default(BoneWeight);
|
|
for (int num6 = 0; num6 < num2; num6++)
|
|
{
|
|
int item = list[num6].Item1;
|
|
float num7 = list[num6].Item2 * num5;
|
|
switch (num6)
|
|
{
|
|
case 0:
|
|
boneWeight.boneIndex0 = item;
|
|
boneWeight.weight0 = num7;
|
|
break;
|
|
case 1:
|
|
boneWeight.boneIndex1 = item;
|
|
boneWeight.weight1 = num7;
|
|
break;
|
|
case 2:
|
|
boneWeight.boneIndex2 = item;
|
|
boneWeight.weight2 = num7;
|
|
break;
|
|
case 3:
|
|
boneWeight.boneIndex3 = item;
|
|
boneWeight.weight3 = num7;
|
|
break;
|
|
}
|
|
}
|
|
array[i] = boneWeight;
|
|
}
|
|
return array;
|
|
}
|
|
|
|
private float[,] InpaintApprox(Vector3[] verts, int[] triangles, float[,] weights, bool[] matched, List<int>[] adjacency, int iterations, float alpha)
|
|
{
|
|
int num = verts.Length;
|
|
int length = weights.GetLength(1);
|
|
float[,] array = (float[,])weights.Clone();
|
|
float[,] array2 = new float[num, length];
|
|
for (int i = 0; i < iterations; i++)
|
|
{
|
|
for (int j = 0; j < num; j++)
|
|
{
|
|
if (matched[j])
|
|
{
|
|
for (int k = 0; k < length; k++)
|
|
{
|
|
array2[j, k] = array[j, k];
|
|
}
|
|
continue;
|
|
}
|
|
List<int> list = adjacency[j];
|
|
if (list == null || list.Count == 0)
|
|
{
|
|
for (int l = 0; l < length; l++)
|
|
{
|
|
array2[j, l] = array[j, l];
|
|
}
|
|
continue;
|
|
}
|
|
for (int m = 0; m < length; m++)
|
|
{
|
|
float num2 = 0f;
|
|
for (int n = 0; n < list.Count; n++)
|
|
{
|
|
int num3 = list[n];
|
|
num2 += array[num3, m];
|
|
}
|
|
float b = num2 / (float)list.Count;
|
|
array2[j, m] = Mathf.Lerp(array[j, m], b, alpha);
|
|
}
|
|
}
|
|
float[,] array3 = array2;
|
|
float[,] array4 = array;
|
|
array = array3;
|
|
array2 = array4;
|
|
}
|
|
return array;
|
|
}
|
|
|
|
private float[,] SmoothWeightsApprox(Vector3[] verts, float[,] weights, bool[] matched, List<int>[] adjacency, int numSmoothIterSteps, float smoothAlpha, float distanceThreshold)
|
|
{
|
|
int num = verts.Length;
|
|
int length = weights.GetLength(1);
|
|
bool[] array = new bool[num];
|
|
for (int i = 0; i < num; i++)
|
|
{
|
|
if (!matched[i])
|
|
{
|
|
this.FloodFillWithinDistance(verts, adjacency, i, distanceThreshold, array);
|
|
}
|
|
}
|
|
float[,] array2 = (float[,])weights.Clone();
|
|
float[,] array3 = new float[num, length];
|
|
for (int j = 0; j < numSmoothIterSteps; j++)
|
|
{
|
|
for (int k = 0; k < num; k++)
|
|
{
|
|
if (!array[k])
|
|
{
|
|
for (int l = 0; l < length; l++)
|
|
{
|
|
array3[k, l] = array2[k, l];
|
|
}
|
|
continue;
|
|
}
|
|
List<int> list = adjacency[k];
|
|
if (list == null || list.Count == 0)
|
|
{
|
|
for (int m = 0; m < length; m++)
|
|
{
|
|
array3[k, m] = array2[k, m];
|
|
}
|
|
continue;
|
|
}
|
|
for (int n = 0; n < length; n++)
|
|
{
|
|
float num2 = 0f;
|
|
for (int num3 = 0; num3 < list.Count; num3++)
|
|
{
|
|
int num4 = list[num3];
|
|
num2 += array2[num4, n];
|
|
}
|
|
float b = num2 / (float)list.Count;
|
|
array3[k, n] = Mathf.Lerp(array2[k, n], b, smoothAlpha);
|
|
}
|
|
}
|
|
float[,] array4 = array3;
|
|
float[,] array5 = array2;
|
|
array2 = array4;
|
|
array3 = array5;
|
|
}
|
|
return array2;
|
|
}
|
|
|
|
private void FloodFillWithinDistance(Vector3[] verts, List<int>[] adjacency, int startIndex, float maxDistance, bool[] visited)
|
|
{
|
|
Queue<int> queue = new Queue<int>();
|
|
queue.Enqueue(startIndex);
|
|
visited[startIndex] = true;
|
|
while (queue.Count > 0)
|
|
{
|
|
int num = queue.Dequeue();
|
|
foreach (int item in adjacency[num])
|
|
{
|
|
if (!visited[item] && (verts[startIndex] - verts[item]).magnitude < maxDistance)
|
|
{
|
|
visited[item] = true;
|
|
queue.Enqueue(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private BoneWeight[] MergeBodyAndAccessoryWeights(float[,] bodyWeights, float[,] accessoryWeights, int bodyBoneCount, float tiny, bool enforceFour)
|
|
{
|
|
int length = bodyWeights.GetLength(0);
|
|
bodyWeights.GetLength(1);
|
|
int length2 = accessoryWeights.GetLength(1);
|
|
BoneWeight[] array = new BoneWeight[length];
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
List<(int, float)> list = new List<(int, float)>();
|
|
float num = 0f;
|
|
for (int j = 0; j < length2; j++)
|
|
{
|
|
float num2 = accessoryWeights[i, j];
|
|
if (num2 > tiny)
|
|
{
|
|
int item = bodyBoneCount + j;
|
|
list.Add((item, num2));
|
|
num += num2;
|
|
}
|
|
}
|
|
List<(int, float)> list2 = new List<(int, float)>();
|
|
float num3 = 0f;
|
|
for (int k = 0; k < bodyBoneCount; k++)
|
|
{
|
|
float num4 = bodyWeights[i, k];
|
|
if (num4 > tiny)
|
|
{
|
|
list2.Add((k, num4));
|
|
num3 += num4;
|
|
}
|
|
}
|
|
if (num3 > tiny && num < 1f - tiny)
|
|
{
|
|
float num5 = Mathf.Max(0f, 1f - num);
|
|
float num6 = num5 / num3;
|
|
for (int l = 0; l < list2.Count; l++)
|
|
{
|
|
list2[l] = (list2[l].Item1, list2[l].Item2 * num6);
|
|
}
|
|
num3 = num5;
|
|
}
|
|
else
|
|
{
|
|
for (int m = 0; m < list2.Count; m++)
|
|
{
|
|
list2[m] = (list2[m].Item1, 0f);
|
|
}
|
|
num3 = 0f;
|
|
}
|
|
List<(int, float)> list3 = new List<(int, float)>();
|
|
list3.AddRange(list);
|
|
list3.AddRange(list2);
|
|
list3.RemoveAll(((int bone, float w) t) => t.w <= tiny);
|
|
if (list3.Count == 0)
|
|
{
|
|
array[i] = default(BoneWeight);
|
|
continue;
|
|
}
|
|
if (enforceFour && list3.Count > 4)
|
|
{
|
|
list3.Sort(delegate ((int bone, float w) a, (int bone, float w) b)
|
|
{
|
|
bool flag = a.bone >= bodyBoneCount;
|
|
bool flag2 = b.bone >= bodyBoneCount;
|
|
return (flag != flag2) ? ((!flag) ? 1 : (-1)) : b.w.CompareTo(a.w);
|
|
});
|
|
list3 = list3.GetRange(0, 4);
|
|
}
|
|
float num7 = 0f;
|
|
foreach (var item3 in list3)
|
|
{
|
|
num7 += item3.Item2;
|
|
}
|
|
if (num7 < tiny)
|
|
{
|
|
num7 = 1f;
|
|
}
|
|
float num8 = 1f / num7;
|
|
BoneWeight boneWeight = default(BoneWeight);
|
|
for (int num9 = 0; num9 < list3.Count && num9 < 4; num9++)
|
|
{
|
|
int item2 = list3[num9].Item1;
|
|
float num10 = list3[num9].Item2 * num8;
|
|
switch (num9)
|
|
{
|
|
case 0:
|
|
boneWeight.boneIndex0 = item2;
|
|
boneWeight.weight0 = num10;
|
|
break;
|
|
case 1:
|
|
boneWeight.boneIndex1 = item2;
|
|
boneWeight.weight1 = num10;
|
|
break;
|
|
case 2:
|
|
boneWeight.boneIndex2 = item2;
|
|
boneWeight.weight2 = num10;
|
|
break;
|
|
case 3:
|
|
boneWeight.boneIndex3 = item2;
|
|
boneWeight.weight3 = num10;
|
|
break;
|
|
}
|
|
}
|
|
array[i] = boneWeight;
|
|
}
|
|
return array;
|
|
}
|
|
|
|
private void EnsureNonZeroAndNormalizeFinalWeights(ClothInstance cloth, BoneWeight[] weights, List<int>[] adjacency, float tiny)
|
|
{
|
|
if (cloth != null && weights != null && weights.Length != 0)
|
|
{
|
|
this.PreNormalizeBoneWeightsInPlace(weights, tiny);
|
|
this.FillZeroWeightsFromNeighbors(weights, adjacency, tiny);
|
|
this.NormalizeAllBoneWeightsInPlace(weights, tiny);
|
|
}
|
|
}
|
|
|
|
private void PreNormalizeBoneWeightsInPlace(BoneWeight[] weights, float tiny)
|
|
{
|
|
for (int i = 0; i < weights.Length; i++)
|
|
{
|
|
BoneWeight boneWeight = weights[i];
|
|
float num = this.Sum(in weights[i]);
|
|
if (!(num < tiny))
|
|
{
|
|
float num2 = 1f / num;
|
|
boneWeight.weight0 *= num2;
|
|
boneWeight.weight1 *= num2;
|
|
boneWeight.weight2 *= num2;
|
|
boneWeight.weight3 *= num2;
|
|
weights[i] = boneWeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void FillZeroWeightsFromNeighbors(BoneWeight[] weights, List<int>[] adjacency, float tiny)
|
|
{
|
|
int num = weights.Length;
|
|
for (int i = 0; i < num; i++)
|
|
{
|
|
if (!(this.Sum(in weights[i]) >= tiny))
|
|
{
|
|
BoneWeight bw = this.AverageFromNeighbors(i, weights, adjacency, tiny, 1);
|
|
if (this.Sum(in bw) >= tiny)
|
|
{
|
|
weights[i] = bw;
|
|
}
|
|
}
|
|
}
|
|
for (int j = 0; j < num; j++)
|
|
{
|
|
if (!(this.Sum(in weights[j]) >= tiny))
|
|
{
|
|
int num2 = this.FindNearestNonZeroVertexBfs(j, weights, adjacency, tiny, 8);
|
|
if (num2 >= 0)
|
|
{
|
|
weights[j] = weights[num2];
|
|
continue;
|
|
}
|
|
weights[j] = new BoneWeight
|
|
{
|
|
boneIndex0 = 0,
|
|
weight0 = 1f
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
private BoneWeight AverageFromNeighbors(int v, BoneWeight[] weights, List<int>[] adjacency, float tiny, int maxRing)
|
|
{
|
|
Dictionary<int, float> map = new Dictionary<int, float>();
|
|
Queue<int> queue = new Queue<int>();
|
|
Dictionary<int, int> dictionary = new Dictionary<int, int>();
|
|
HashSet<int> hashSet = new HashSet<int>();
|
|
queue.Enqueue(v);
|
|
dictionary[v] = 0;
|
|
hashSet.Add(v);
|
|
while (queue.Count > 0)
|
|
{
|
|
int num = queue.Dequeue();
|
|
int num2 = dictionary[num];
|
|
if (num2 >= maxRing)
|
|
{
|
|
continue;
|
|
}
|
|
List<int> list = adjacency[num];
|
|
if (list == null)
|
|
{
|
|
continue;
|
|
}
|
|
foreach (int item in list)
|
|
{
|
|
if (item >= 0 && item < weights.Length && hashSet.Add(item))
|
|
{
|
|
dictionary[item] = num2 + 1;
|
|
queue.Enqueue(item);
|
|
BoneWeight bw = weights[item];
|
|
if (!(this.Sum(in bw) < tiny))
|
|
{
|
|
this.Acc(map, bw.boneIndex0, bw.weight0, tiny);
|
|
this.Acc(map, bw.boneIndex1, bw.weight1, tiny);
|
|
this.Acc(map, bw.boneIndex2, bw.weight2, tiny);
|
|
this.Acc(map, bw.boneIndex3, bw.weight3, tiny);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return this.BuildTop4Normalized(map, tiny);
|
|
}
|
|
|
|
private int FindNearestNonZeroVertexBfs(int start, BoneWeight[] weights, List<int>[] adjacency, float tiny, int maxDepth)
|
|
{
|
|
Queue<int> queue = new Queue<int>();
|
|
Dictionary<int, int> dictionary = new Dictionary<int, int>();
|
|
HashSet<int> hashSet = new HashSet<int>();
|
|
queue.Enqueue(start);
|
|
dictionary[start] = 0;
|
|
hashSet.Add(start);
|
|
while (queue.Count > 0)
|
|
{
|
|
int num = queue.Dequeue();
|
|
int num2 = dictionary[num];
|
|
if (num2 > 0 && this.Sum(in weights[num]) >= tiny)
|
|
{
|
|
return num;
|
|
}
|
|
if (num2 >= maxDepth)
|
|
{
|
|
continue;
|
|
}
|
|
List<int> list = adjacency[num];
|
|
if (list == null)
|
|
{
|
|
continue;
|
|
}
|
|
foreach (int item in list)
|
|
{
|
|
if (item >= 0 && item < weights.Length && hashSet.Add(item))
|
|
{
|
|
dictionary[item] = num2 + 1;
|
|
queue.Enqueue(item);
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private void NormalizeAllBoneWeightsInPlace(BoneWeight[] weights, float tiny)
|
|
{
|
|
for (int i = 0; i < weights.Length; i++)
|
|
{
|
|
float num = this.Sum(in weights[i]);
|
|
if (!(num < tiny))
|
|
{
|
|
float num2 = 1f / num;
|
|
BoneWeight boneWeight = weights[i];
|
|
boneWeight.weight0 *= num2;
|
|
boneWeight.weight1 *= num2;
|
|
boneWeight.weight2 *= num2;
|
|
boneWeight.weight3 *= num2;
|
|
weights[i] = boneWeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
private float Sum(in BoneWeight bw)
|
|
{
|
|
return bw.weight0 + bw.weight1 + bw.weight2 + bw.weight3;
|
|
}
|
|
|
|
private void Acc(Dictionary<int, float> map, int boneIndex, float w, float tiny)
|
|
{
|
|
if (boneIndex >= 0 && !(w <= tiny))
|
|
{
|
|
if (map.TryGetValue(boneIndex, out var value))
|
|
{
|
|
map[boneIndex] = value + w;
|
|
}
|
|
else
|
|
{
|
|
map[boneIndex] = w;
|
|
}
|
|
}
|
|
}
|
|
|
|
private BoneWeight BuildTop4Normalized(Dictionary<int, float> map, float tiny)
|
|
{
|
|
if (map == null || map.Count == 0)
|
|
{
|
|
return default(BoneWeight);
|
|
}
|
|
List<KeyValuePair<int, float>> list = map.OrderByDescending((KeyValuePair<int, float> kv) => kv.Value).Take(4).ToList();
|
|
float num = 0f;
|
|
for (int num2 = 0; num2 < list.Count; num2++)
|
|
{
|
|
num += list[num2].Value;
|
|
}
|
|
if (num < tiny)
|
|
{
|
|
return default(BoneWeight);
|
|
}
|
|
float num3 = 1f / num;
|
|
BoneWeight result = default(BoneWeight);
|
|
for (int num4 = 0; num4 < list.Count; num4++)
|
|
{
|
|
int key = list[num4].Key;
|
|
float num5 = list[num4].Value * num3;
|
|
switch (num4)
|
|
{
|
|
case 0:
|
|
result.boneIndex0 = key;
|
|
result.weight0 = num5;
|
|
break;
|
|
case 1:
|
|
result.boneIndex1 = key;
|
|
result.weight1 = num5;
|
|
break;
|
|
case 2:
|
|
result.boneIndex2 = key;
|
|
result.weight2 = num5;
|
|
break;
|
|
case 3:
|
|
result.boneIndex3 = key;
|
|
result.weight3 = num5;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|