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();
}
}
}