using System.Collections; using System.Collections.Generic; using UnityEngine; using System; namespace TriLib.Extras { /// /// Represents an avatar loader behaviour. The main class used to load avatars. /// public class AvatarLoader : MonoBehaviour { /// /// Current Avatar reference. /// public GameObject CurrentAvatar; /// /// AnimationController that will be assigned to the loaded object. /// public RuntimeAnimatorController RuntimeAnimatorController; /// /// Amount by which the arm's length is allowed to stretch when using IK. /// public float ArmStretch = 0.05f; /// /// Modification to the minimum distance between the feet of a humanoid model. /// public float FeetSpacing = 0f; /// /// True for any human that has a translation Degree of Freedom (DoF). It is set to false by default. /// public bool HasTranslationDof = false; /// /// Amount by which the leg's length is allowed to stretch when using IK. /// public float LegStretch = 0.5f; /// /// Defines how the lower arm's roll/twisting is distributed between the elbow and wrist joints. /// public float LowerArmTwist = 0.5f; /// /// Defines how the lower leg's roll/twisting is distributed between the knee and ankle. /// public float LowerLegTwist = 0.5f; /// /// Defines how the lower arm's roll/twisting is distributed between the shoulder and elbow joints. /// public float UpperArmTwist = 0.5f; /// /// Defines how the upper leg's roll/twisting is distributed between the thigh and knee joints. /// public float UpperLegTwist = 0.5f; /// /// Object loading scale. /// public float Scale = 0.01f; /// /// Offset applied to CapsuleCollider height. /// public float HeightOffset = 0.01f; /// /// Use this field to define custom Human Bone to Unity Bone relationship, you can follow the tempĺates bellow. /// public BoneRelationshipList CustomBoneNames; /// /// 3Ds Max Biped files to Unity human bone relationship. /// private static readonly BoneRelationshipList BipedBoneNames = new BoneRelationshipList { {"Head", "Head", false}, {"Neck", "Neck", true}, {"Chest", "Spine3", true}, {"UpperChest", "Spine1", true}, {"Spine", "Spine", false}, {"Hips", "Bip01", false}, {"LeftShoulder", "L Clavicle", true}, {"LeftUpperArm", "L UpperArm", false}, {"LeftLowerArm", "L Forearm", false}, {"LeftHand", "L Hand", false}, {"RightShoulder", "R Clavicle", true}, {"RightUpperArm", "R UpperArm", false}, {"RightLowerArm", "R Forearm", false}, {"RightHand", "R Hand", false}, {"LeftUpperLeg", "L Thigh", false}, {"LeftLowerLeg", "L Calf", false}, {"LeftFoot", "L Foot", false}, {"LeftToes", "L Toe0", true}, {"RightUpperLeg", "R Thigh", false}, {"RightLowerLeg", "R Calf", false}, {"RightFoot", "R Foot", false}, {"RightToes", "R Toe0", true}, {"Left Thumb Proximal", "L Finger0", true}, {"Left Thumb Intermediate", "L Finger01", true}, {"Left Thumb Distal", "L Finger02", true}, {"Left Index Proximal", "L Finger1", true}, {"Left Index Intermediate", "L Finger11", true}, {"Left Index Distal", "L Finger12", true}, {"Left Middle Proximal", "L Finger2", true}, {"Left Middle Intermediate", "L Finger21", true}, {"Left Middle Distal", "L Finger22", true}, {"Left Ring Proximal", "L Finger3", true}, {"Left Ring Intermediate", "L Finger31", true}, {"Left Ring Distal", "L Finger32", true}, {"Left Little Proximal", "L Finger4", true}, {"Left Little Intermediate", "L Finger41", true}, {"Left Little Distal", "L Finger42", true}, {"Right Thumb Proximal", "R Finger0", true}, {"Right Thumb Intermediate", "R Finger01", true}, {"Right Thumb Distal", "R Finger02", true}, {"Right Index Proximal", "R Finger1", true}, {"Right Index Intermediate", "R Finger11", true}, {"Right Index Distal", "R Finger12", true}, {"Right Middle Proximal", "R Finger2", true}, {"Right Middle Intermediate", "R Finger21", true}, {"Right Middle Distal", "R Finger22", true}, {"Right Ring Proximal", "R Finger3", true}, {"Right Ring Intermediate", "R Finger31", true}, {"Right Ring Distal", "R Finger32", true}, {"Right Little Proximal", "R Finger4", true}, {"Right Little Intermediate", "R Finger41", true}, {"Right Little Distal", "R Finger42", true} }; /// /// Mixamo files to Unity human bone relationship. /// private static readonly BoneRelationshipList MixamoBoneNames = new BoneRelationshipList { {"Head", "Head", false}, {"Neck", "Neck", true}, {"Chest", "Spine1", true}, {"UpperChest", "Spine2", true}, {"Spine", "Spine", false}, {"Hips", "Hips", false}, {"LeftShoulder", "LeftShoulder", true}, {"LeftUpperArm", "LeftArm", false}, {"LeftLowerArm", "LeftForeArm", false}, {"LeftHand", "LeftHand", false}, {"RightShoulder", "RightShoulder", true}, {"RightUpperArm", "RightArm", false}, {"RightLowerArm", "RightForeArm", false}, {"RightHand", "RightHand", false}, {"LeftUpperLeg", "LeftUpLeg", false}, {"LeftLowerLeg", "LeftLeg", false}, {"LeftFoot", "LeftFoot", false}, {"LeftToes", "LeftToeBase", true}, {"RightUpperLeg", "RightUpLeg", false}, {"RightLowerLeg", "RightLeg", false}, {"RightFoot", "RightFoot", false}, {"RightToes", "RightToeBase", true}, {"Left Thumb Proximal", "LeftHandThumb1", true}, {"Left Thumb Intermediate", "LeftHandThumb2", true}, {"Left Thumb Distal", "LeftHandThumb3", true}, {"Left Index Proximal", "LeftHandIndex1", true}, {"Left Index Intermediate", "LeftHandIndex2", true}, {"Left Index Distal", "LeftHandIndex3", true}, {"Left Middle Proximal", "LeftHandMiddle1", true}, {"Left Middle Intermediate", "LeftHandMiddle2", true}, {"Left Middle Distal", "LeftHandMiddle3", true}, {"Left Ring Proximal", "LeftHandRing1", true}, {"Left Ring Intermediate", "LeftHandRing2", true}, {"Left Ring Distal", "LeftHandRing3", true}, {"Left Little Proximal", "LeftHandPinky1", true}, {"Left Little Intermediate", "LeftHandPinky2", true}, {"Left Little Distal", "LeftHandPinky3", true}, {"Right Thumb Proximal", "RightHandThumb1", true}, {"Right Thumb Intermediate", "RightHandThumb2", true}, {"Right Thumb Distal", "RightHandThumb3", true}, {"Right Index Proximal", "RightHandIndex1", true}, {"Right Index Intermediate", "RightHandIndex2", true}, {"Right Index Distal", "RightHandIndex3", true}, {"Right Middle Proximal", "RightHandMiddle1", true}, {"Right Middle Intermediate", "RightHandMiddle2", true}, {"Right Middle Distal", "RightHandMiddle3", true}, {"Right Ring Proximal", "RightHandRing1", true}, {"Right Ring Intermediate", "RightHandRing2", true}, {"Right Ring Distal", "RightHandRing3", true}, {"Right Little Proximal", "RightHandPinky1", true}, {"Right Little Intermediate", "RightHandPinky2", true}, {"Right Little Distal", "RightHandPinky3", true} }; /// /// Sample loading options. /// private AssetLoaderOptions _loaderOptions; /// /// Setups the Avatar Loader. /// protected void Start() { _loaderOptions = AssetLoaderOptions.CreateInstance(); _loaderOptions.UseLegacyAnimations = false; _loaderOptions.DontGenerateAvatar = true; _loaderOptions.AnimatorController = RuntimeAnimatorController; } /// /// Loads the avatar from specified filename. /// /// Avatar file data. /// File extension. /// Template . /// true, if avatar was loaded, false otherwise. public bool LoadAvatarFromMemory(byte[] data, string extension, GameObject templateAvatar) { GameObject loadedObject; if (CurrentAvatar != null) { Destroy(CurrentAvatar); } try { using (var assetLoader = new AssetLoader()) { loadedObject = assetLoader.LoadFromMemoryWithTextures(data, extension, _loaderOptions, templateAvatar); } } #if TRILIB_OUTPUT_MESSAGES catch (Exception exception) { Debug.LogError(exception.ToString()); return false; } #else catch { if (CurrentAvatar != null) { Destroy(CurrentAvatar); } return false; } #endif if (loadedObject != null) { if (templateAvatar != null) { loadedObject.transform.parent = templateAvatar.transform; CurrentAvatar = templateAvatar; } else { CurrentAvatar = loadedObject; } CurrentAvatar.transform.localScale = Vector3.one * Scale; CurrentAvatar.tag = "Player"; SetupCapsuleCollider(); return BuildAvatar(); } return false; } /// /// Loads the avatar from specified filename. /// /// Avatar filename. /// Template . /// true, if avatar was loaded, false otherwise. public bool LoadAvatar(string filename, GameObject templateAvatar) { GameObject loadedObject; if (CurrentAvatar != null) { Destroy(CurrentAvatar); } try { using (var assetLoader = new AssetLoader()) { loadedObject = assetLoader.LoadFromFile(filename, _loaderOptions, templateAvatar); } } #if TRILIB_OUTPUT_MESSAGES catch (Exception exception) { Debug.LogError(exception.ToString()); return false; } #else catch { if (CurrentAvatar != null) { Destroy(CurrentAvatar); } return false; } #endif if (loadedObject != null) { if (templateAvatar != null) { loadedObject.transform.parent = templateAvatar.transform; CurrentAvatar = templateAvatar; } else { CurrentAvatar = loadedObject; } CurrentAvatar.transform.localScale = Vector3.one * Scale; CurrentAvatar.tag = "Player"; SetupCapsuleCollider(); return BuildAvatar(); } return false; } /// /// Builds the object avatar, based on pre-defined templates (Mixamo, Biped), or based on the , if it's not null or empty. /// /// true if avatar was built, false otherwise. private bool BuildAvatar() { var animator = CurrentAvatar.GetComponent(); if (animator == null) { #if TRILIB_OUTPUT_MESSAGES Debug.LogError("No animator component found on current avatar"); #endif return false; } var skeletonBones = new List(); var humanBones = new List(); var boneTransforms = FindOutBoneTransforms(CurrentAvatar); if (boneTransforms.Count == 0) { #if TRILIB_OUTPUT_MESSAGES Debug.LogError("No suitable bones format found"); #endif return false; } foreach (var boneTransform in boneTransforms) { humanBones.Add(CreateHumanBone(boneTransform.Key, boneTransform.Value.name)); } var transforms = CurrentAvatar.GetComponentsInChildren(); var rootTransform = transforms[1]; skeletonBones.Add(CreateSkeletonBone(rootTransform)); rootTransform.localEulerAngles = Vector3.zero; for (var i = 0; i < transforms.Length; i++) { var childTransform = transforms[i]; var meshRenderers = childTransform.GetComponentsInChildren(); if (meshRenderers.Length > 0) { continue; } var skinnedMeshRenderers = childTransform.GetComponentsInChildren(); if (skinnedMeshRenderers.Length > 0) { continue; } skeletonBones.Add(CreateSkeletonBone(childTransform)); } var humanDescription = new HumanDescription(); humanDescription.armStretch = ArmStretch; humanDescription.feetSpacing = FeetSpacing; humanDescription.hasTranslationDoF = HasTranslationDof; humanDescription.legStretch = LegStretch; humanDescription.lowerArmTwist = LowerArmTwist; humanDescription.lowerLegTwist = LowerLegTwist; humanDescription.upperArmTwist = UpperArmTwist; humanDescription.upperLegTwist = UpperLegTwist; humanDescription.skeleton = skeletonBones.ToArray(); humanDescription.human = humanBones.ToArray(); animator.avatar = AvatarBuilder.BuildHumanAvatar(CurrentAvatar, humanDescription); return true; } /// /// Figures out the bone hierarchy for Avatar building. /// /// The bone hierarchy. /// Previously loaded object. private Dictionary FindOutBoneTransforms(GameObject loadedObject) { var boneTransforms = new Dictionary(); var boneRelationshipLists = new List(); boneRelationshipLists.Add(BipedBoneNames); boneRelationshipLists.Add(MixamoBoneNames); if (CustomBoneNames != null) { boneRelationshipLists.Add(CustomBoneNames); } var lastBonesValid = false; foreach (var boneRelationshipList in boneRelationshipLists) { if (lastBonesValid) { break; } lastBonesValid = true; foreach (var boneRelationship in boneRelationshipList) { var boneTransform = loadedObject.transform.FindDeepChild(boneRelationship.BoneName, true); if (boneTransform == null) { if (!boneRelationship.Optional) { boneTransforms.Clear(); lastBonesValid = false; break; } continue; } boneTransforms.Add(boneRelationship.HumanBone, boneTransform); } } return boneTransforms; } /// /// Setups the avatar Capsule Collider to encapsulate the loaded object. /// private void SetupCapsuleCollider() { var capsuleCollider = CurrentAvatar.GetComponent(); if (capsuleCollider == null) { return; } var bounds = CurrentAvatar.transform.EncapsulateBounds(); var fraction = 1f / Scale; var boundExtentsX = bounds.extents.x * fraction; var boundExtentsY = bounds.extents.y * fraction; var boundExtentsZ = bounds.extents.z * fraction; capsuleCollider.height = (float)Math.Round(boundExtentsY * 2f, 1); capsuleCollider.radius = (float)Math.Round(Mathf.Sqrt(boundExtentsX * boundExtentsX + boundExtentsZ * boundExtentsZ) * 0.5f, 1); capsuleCollider.center = new Vector3(0f, (float)Math.Round(boundExtentsY, 1) + HeightOffset, 0f); } /// /// Builds a SkeletonBone. /// /// The built skeleton bone. /// The bone Transform to build the Skeleton from. private static SkeletonBone CreateSkeletonBone(Transform boneTransform) { var skeletonBone = new SkeletonBone(); skeletonBone.name = boneTransform.name; skeletonBone.position = boneTransform.localPosition; skeletonBone.rotation = boneTransform.localRotation; skeletonBone.scale = boneTransform.localScale; return skeletonBone; } /// /// Builds a Human Bone. /// /// The human bone. /// Human name. /// Bone name. private static HumanBone CreateHumanBone(string humanName, string boneName) { var humanBone = new HumanBone(); humanBone.boneName = boneName; humanBone.humanName = humanName; humanBone.limit.useDefaultValues = true; return humanBone; } } /// /// Represents a human bone to Unity bone relationship. /// public class BoneRelationship { public string HumanBone; //Human Bone name. public string BoneName; //Unity Bone name. public bool Optional; //Is this bone optional? /// /// Initializes a new instance of the class. /// /// Human bone. /// Bone name. /// If set to true this bone relationship will be optional in the hierarchy. public BoneRelationship(string humanBone, string boneName, bool optional) { HumanBone = humanBone; BoneName = boneName; Optional = optional; } } /// /// Represents a BoneRelationship list. /// [Serializable] public class BoneRelationshipList : IEnumerable { private readonly List _relationships; //Relationship list /// /// Initializes a new instance of the class. /// public BoneRelationshipList() { _relationships = new List(); } /// /// Adds a new BoneRelationship to this list. /// /// Human bone. /// Bone name. /// If set to true this bone relationship will be optional in the hierarchy. public void Add(string humanBone, string boneName, bool optional) { _relationships.Add(new BoneRelationship(humanBone, boneName, optional)); } /// /// Returns an enumerator that iterates through this collection. /// /// The enumerator. public IEnumerator GetEnumerator() { return _relationships.GetEnumerator(); } /// /// Gets the enumerator for this collection. /// /// The enumerator. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }