Files
Fishing2/Assets/FImpossible Creations/Plugins - Animating/Tail Animator/Utilities/Code/TailAnimator.Skinning.API.cs
2025-11-10 00:08:26 +08:00

266 lines
12 KiB
C#

using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace FIMSpace.FTail
{
/// <summary>
/// FCr: Part of Tail Animator Skinning Static Meshes API
/// Methods used by skinner to create skinned mesh renderers from static meshes
/// </summary>
public static class FTail_Skinning
{
/// <summary>
/// Calculating base vertices datas for provided bones setup
/// </summary>
/// <param name="baseMesh"> Mesh to be weighted </param>
/// <param name="bonesCoords"> Required local positions and rotations for bones </param>
/// <param name="spreadOffset"> Origin weighting offset which can be helpful in some cases, it can be Vector3.zero in most cases </param>
/// <param name="weightBoneLimit"> To how many bones vertex can be weighted to create smooth weight effect </param>
/// <param name="spreadValue"> Smoothing weights on the edges of bones if lower then more smooth but don't oversmooth it </param>
/// <param name="spreadPower"> Making smoothing more sharp on edges </param>
public static FTail_SkinningVertexData[] CalculateVertexWeightingData(Mesh baseMesh, Transform[] bonesCoords, Vector3 spreadOffset, int weightBoneLimit = 2, float spreadValue = 0.8f, float spreadPower = 0.185f)
{
Vector3[] pos = new Vector3[bonesCoords.Length];
Quaternion[] rot = new Quaternion[bonesCoords.Length];
//for (int i = 0; i < bonesCoords.Length; i++)
//{
// pos[i] = bonesCoords[i].position;
// rot[i] = bonesCoords[i].rotation;
//}
// We must reset bones structure to identity space
for (int i = 0; i < bonesCoords.Length; i++)
{
// Transforming from world to local space coords
pos[i] = bonesCoords[0].parent.InverseTransformPoint(bonesCoords[i].position);
rot[i] = FEngineering.QToLocal(bonesCoords[0].parent.rotation, bonesCoords[i].rotation);
}
return CalculateVertexWeightingData(baseMesh, pos, rot, spreadOffset, weightBoneLimit, spreadValue, spreadPower);
}
/// <summary>
/// Calculating base vertices datas for provided bones setup
/// </summary>
/// <param name="baseMesh"> Mesh to be weighted </param>
/// <param name="bonesPos"> Mesh local space positions for bones </param>
/// <param name="bonesRot"> Mesh local space rotations for bones </param>
/// <param name="bonesCoords"> Required local positions and rotations for bones </param>
/// <param name="spreadOffset"> Origin weighting offset which can be helpful in some cases, it can be Vector3.zero in most cases </param>
/// <param name="weightBoneLimit"> To how many bones vertex can be weighted to create smooth weight effect </param>
/// <param name="spreadValue"> Smoothing weights on the edges of bones if lower then more smooth but don't oversmooth it </param>
/// <param name="spreadPower"> Making smoothing more sharp on edges </param>
public static FTail_SkinningVertexData[] CalculateVertexWeightingData(Mesh baseMesh, Vector3[] bonesPos, Quaternion[] bonesRot, Vector3 spreadOffset, int weightBoneLimit = 2, float spreadValue = 0.8f, float spreadPower = 0.185f)
{
if (weightBoneLimit < 1) weightBoneLimit = 1;
if (weightBoneLimit > 2) weightBoneLimit = 2; // Limiting for now
#region Editor progress dialogs
#if UNITY_EDITOR
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
#endif
#endregion
int vertCount = baseMesh.vertexCount;
FTail_SkinningVertexData[] vertexDatas = new FTail_SkinningVertexData[vertCount];
// Computing helper segments for weighting bones
Vector3[] boneAreas = new Vector3[bonesPos.Length];
for (int i = 0; i < bonesPos.Length - 1; i++)
{
// Direction vector towards further bone
boneAreas[i] = bonesPos[i + 1] - bonesPos[i]; //bonesCoords[i + 1].localPosition - bonesCoords[i].localPosition;
}
if (boneAreas.Length > 1) boneAreas[boneAreas.Length - 1] = boneAreas[boneAreas.Length - 2];
#if UNITY_EDITOR
try
{
for (int i = 0; i < vertCount; i++)
{
vertexDatas[i] = new FTail_SkinningVertexData(baseMesh.vertices[i]);
vertexDatas[i].CalculateVertexParameters(bonesPos, bonesRot, boneAreas, weightBoneLimit, spreadValue, spreadOffset, spreadPower);
#region Editor progress dialogs
if (!Application.isPlaying)
// Displaying progress bar when iteration takes too much time
if (watch.ElapsedMilliseconds > 1500)
if (i % 10 == 0)
EditorUtility.DisplayProgressBar("Analizing mesh vertices...", "Analizing Vertices (" + i + "/" + vertCount + ")", ((float)i / (float)vertCount));
#endregion
}
#region Editor progress dialogs
if (!Application.isPlaying)
EditorUtility.ClearProgressBar();
#endregion
}
catch (System.Exception exc)
{
Debug.LogError(exc);
#region Editor progress dialogs
if (!Application.isPlaying)
EditorUtility.ClearProgressBar();
#endregion
}
#else
for (int i = 0; i < vertCount; i++)
{
vertexDatas[i] = new FTail_SkinningVertexData(baseMesh.vertices[i]);
vertexDatas[i].CalculateVertexParameters(bonesPos, bonesRot, boneAreas, weightBoneLimit, spreadValue, spreadOffset, spreadPower);
}
#endif
return vertexDatas;
}
/// <summary>
/// Skinning target mesh with helper vertex datas which you can get with CalculateVertexWeightingData() method
/// Using transforms as guidement for bones positions and rotations
/// </summary>
/// <returns> Skinned mesh can't be returned as 'Mesh' type because skinned mesh is mesh + bones transforms etc. </returns>
public static SkinnedMeshRenderer SkinMesh(Mesh baseMesh, Transform skinParent, Transform[] bonesStructure, FTail_SkinningVertexData[] vertData)
{
Vector3[] pos = new Vector3[bonesStructure.Length];
Quaternion[] rot = new Quaternion[bonesStructure.Length];
// We must reset bones structure to identity space
for (int i = 0; i < bonesStructure.Length; i++)
{
// Transforming from world to local space coords
pos[i] = skinParent.InverseTransformPoint(bonesStructure[i].position);
rot[i] = FEngineering.QToLocal(skinParent.rotation, bonesStructure[i].rotation);
}
SkinnedMeshRenderer skin = SkinMesh(baseMesh, pos, rot, vertData);
return skin;
}
/// <summary>
/// Skinning target mesh with helper vertex datas which you can
/// </summary>
/// <param name="baseMesh"> Base static mesh to be skinned </param>
/// <param name="bonesPositions"> Bones positions in mesh local space </param>
/// <param name="bonesRotations"> Bones rotations in mesh local space </param>
/// <param name="vertData"> Get it with CalculateVertexWeightingData() method </param>
/// <returns> Skinned mesh can't be returned as 'Mesh' type because skinned mesh is mesh + bones transforms etc. </returns>
public static SkinnedMeshRenderer SkinMesh(Mesh baseMesh, Vector3[] bonesPositions, Quaternion[] bonesRotations, FTail_SkinningVertexData[] vertData)
{
if (bonesPositions == null) return null;
if (bonesRotations == null) return null;
if (baseMesh == null) return null;
if (vertData == null) return null;
// Creating copy of target mesh and refreshing it
Mesh newMesh = GameObject.Instantiate(baseMesh);
newMesh.name = baseMesh.name + " [FSKINNED]";
// Preparing new object which will have skinned mesh renderer with new mesh and bones in it
GameObject newSkinObject = new GameObject(baseMesh.name + " [FSKINNED]");
Transform newParent = newSkinObject.transform;
// Preparing skin
SkinnedMeshRenderer skin = newParent.gameObject.AddComponent<SkinnedMeshRenderer>();
// Preparing bones for weighting
Transform[] bones = new Transform[bonesPositions.Length];
Matrix4x4[] bindPoses = new Matrix4x4[bonesPositions.Length];
string nameString;
if (baseMesh.name.Length < 6) nameString = baseMesh.name; else nameString = baseMesh.name.Substring(0, 5);
for (int i = 0; i < bonesPositions.Length; i++)
{
bones[i] = new GameObject("BoneF-" + nameString + "[" + i + "]").transform;
if (i == 0) bones[i].SetParent(newParent, true); else bones[i].SetParent(bones[i - 1], true);
bones[i].transform.position = bonesPositions[i];
bones[i].transform.rotation = bonesRotations[i];
bindPoses[i] = bones[i].worldToLocalMatrix * newParent.localToWorldMatrix;
}
BoneWeight[] weights = new BoneWeight[newMesh.vertexCount];
for (int v = 0; v < weights.Length; v++) weights[v] = new BoneWeight();
// Calculating and applying weights for verices
for (int i = 0; i < vertData.Length; i++)
{
for (int w = 0; w < vertData[i].weights.Length; w++)
{
weights[i] = SetWeightIndex(weights[i], w, vertData[i].bonesIndexes[w]);
weights[i] = SetWeightToBone(weights[i], w, vertData[i].weights[w]);
}
}
newMesh.bindposes = bindPoses;
newMesh.boneWeights = weights;
List<Vector3> normals = new List<Vector3>();
List < Vector4> tangents = new List<Vector4>();
baseMesh.GetNormals(normals);
baseMesh.GetTangents(tangents);
newMesh.SetNormals(normals);
newMesh.SetTangents(tangents);
newMesh.bounds = baseMesh.bounds;
// Applying generated mesh to skin controller
skin.sharedMesh = newMesh;
skin.rootBone = bones[0];
skin.bones = bones;
return skin;
}
/// <summary>
/// Method which is setting certain weight variable from BoneWeight struct
/// </summary>
public static BoneWeight SetWeightIndex(BoneWeight weight, int bone = 0, int index = 0)
{
switch (bone)
{
case 1: weight.boneIndex1 = index; break;
case 2: weight.boneIndex2 = index; break;
case 3: weight.boneIndex3 = index; break;
default: weight.boneIndex0 = index; break;
}
return weight;
}
/// <summary>
/// Method which is setting certain weight variable from BoneWeight struct
/// </summary>
public static BoneWeight SetWeightToBone(BoneWeight weight, int bone = 0, float value = 1f)
{
switch (bone)
{
case 1: weight.weight1 = value; break;
case 2: weight.weight2 = value; break;
case 3: weight.weight3 = value; break;
default: weight.weight0 = value; break;
}
return weight;
}
}
}