您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

391 行
16 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine.Perception.GroundTruth;
namespace UnityEngine.Perception.Content
{
public static class CharacterValidation
{
public static string[] RequiredBones =
{
"Head",
"Hips",
"Spine",
"LeftUpperArm",
"LeftLowerArm",
"LeftHand",
"RightUpperArm",
"RightLowerArm",
"RightHand",
"LeftUpperLeg",
"LeftLowerLeg",
"LeftFoot",
"RightUpperLeg",
"RightLowerLeg",
"RightFoot",
};
/// <summary>
/// Checks the selected character fbx or prefab to make sure the required bones defined bu the strong [] RequiredBones
/// </summary>
/// <param name="selection">GameObject selected by the user in the editor</param>
/// <returns>Dictionary of the Human Bone and bool to track presence</returns>
public static Dictionary<HumanBone, bool> AvatarRequiredBones(GameObject selection)
{
var result = new Dictionary<HumanBone, bool>();
var selectionInstance = (GameObject)PrefabUtility.InstantiatePrefab(selection);
if (selectionInstance != null)
selection = selectionInstance;
var animator = selection.GetComponentInChildren<Animator>();
var bone = new HumanBone();
if (animator == null)
{
Debug.LogWarning("Animator and/or the Skinned Mesh Renderer are missing or can't be found!");
result.Add(bone, false);
GameObject.DestroyImmediate(selection);
return result;
}
var human = animator.avatar.humanDescription.human;
var totalBones = 0;
for(int h = 0; h < human.Length; h++)
{
for (int b = 0; b < RequiredBones.Length; b++)
{
if(human[h].humanName == RequiredBones[b])
{
if (human[h].boneName != null)
{
bone.boneName = human[h].boneName;
totalBones = totalBones = +1;
result.Add(bone, true);
}
else
{
result.Add(bone, false);
}
}
}
}
GameObject.DestroyImmediate(selection);
return result;
}
/// <summary>
/// Based on the selection of a fbx or prefab character will find the location of the nose, left ear, and right ear joints
/// based on eye and the center head locations.
/// </summary>
/// <param name="selection"> fbx or prefab selection</param>
/// <param name="savePath">Path where the new created prefab will be saved too</param>
/// <param name="drawRays">Shows the rays on how the joint posiitons are found</param>
/// <returns></returns>
public static GameObject AvatarCreateNoseEars (GameObject selection, Object keypointTemplate, string savePath, bool drawRays = false)
{
if (selection == null)
{
Debug.LogWarning("Selected Game Object is null or missing");
return new GameObject("Failed");
}
var selectionInstance = (GameObject)PrefabUtility.InstantiatePrefab(selection);
if (selectionInstance != null)
selection = selectionInstance;
var animator = selection.GetComponentInChildren<Animator>();
var skinnedMeshRenderer = selection.GetComponentInChildren<SkinnedMeshRenderer>();
if (animator == null || skinnedMeshRenderer == null)
{
Debug.LogWarning("Animator and/or the Skinned Mesh Renderer are missing or can't be found!");
return new GameObject("Failed");
}
var human = animator.avatar.humanDescription.human;
var skeleton = animator.avatar.humanDescription.skeleton;
var verticies = skinnedMeshRenderer.sharedMesh.vertices;
var head = animator.GetBoneTransform(HumanBodyBones.Head);
//Currently stage left and stage right for the eyes
var leftEye = animator.GetBoneTransform(HumanBodyBones.RightEye);
var rightEye = animator.GetBoneTransform(HumanBodyBones.LeftEye);
var faceCenter = Vector3.zero;
var earCenter = Vector3.zero;
var nosePos = Vector3.zero;
var earRightPos = Vector3.zero;
var earLeftPos = Vector3.zero;
var distanceCheck = 1f;
var eyeDistance = 0f;
var directionLeft = Vector3.zero;
var directionRight = Vector3.zero;
var rayHead = new Ray();
var rayLeftEye = new Ray();
var rayRightEye = new Ray();
var noseRayFor = new Ray();
var rayNoseBack = new Ray();
var rayEarLeft = new Ray();
var rayEarRight = new Ray();
if(leftEye == null || rightEye == null)
{
Debug.LogWarning("Eye positions are null, unable to position nose joint!");
return new GameObject("Failed");
}
else
{
// getting the angle direction from the start point of the eyes and the distance between the left and right eyes
eyeDistance = Vector3.Distance(leftEye.position, rightEye.position);
directionLeft = Quaternion.AngleAxis(-45, -leftEye.right) * -leftEye.up;
directionRight = Quaternion.AngleAxis(-45, rightEye.right) * -rightEye.up;
}
rayLeftEye.origin = leftEye.position;
rayLeftEye.direction = directionLeft * eyeDistance;
rayRightEye.origin = rightEye.position;
rayRightEye.direction = directionRight * eyeDistance;
// Find the center of the face by taking where the left and right eye rays drawn at 45 degrees intersect to find
// the average point of the nose
for (var i = 0f; i < distanceCheck; i += 0.01f)
{
var pointR = rayRightEye.GetPoint(i);
var pointL = rayLeftEye.GetPoint(i);
var distanceX = Math.Abs(pointR.x - pointL.x);
var distanceY = Math.Abs(pointR.y - pointL.y);
if (distanceX < 0.01 && distanceY < 0.01 )
{
faceCenter = pointR;
}
}
rayHead.origin = head.position;
rayHead.direction = Vector3.up * distanceCheck;
noseRayFor.origin = faceCenter;
noseRayFor.direction = Vector3.forward * distanceCheck;
rayNoseBack.origin = faceCenter;
rayNoseBack.direction = Vector3.back * distanceCheck;
// Find the ear center which can be used to go left and right to find the edge of the mesh for the ear
for (var i = 0f; i < distanceCheck; i += 0.01f)
{
var pointH = rayHead.GetPoint(i);
var pointF = rayNoseBack.GetPoint(i);
var distanceZ = Math.Abs(pointH.z - pointF.z);
if (distanceZ < 0.01f)
{
earCenter = pointF;
}
}
var points = new List<Vector3>();
// Find the position of the nose using the nose ray from the starting point found by the eyes
for (int v = 0; v < verticies.Length; v++)
{
for (var c = eyeDistance / 2; c < distanceCheck; c += 0.001f)
{
var pointNoseRay = noseRayFor.GetPoint(c);
var pointVert = verticies[v];
var def = 0.003f;
var offset = pointVert - pointNoseRay;
var len = offset.sqrMagnitude;
if(len < def * def || len < def)
{
nosePos = pointNoseRay;
}
}
}
rayEarRight.origin = earCenter;
rayEarRight.direction = Vector3.right * distanceCheck;
rayEarLeft.origin = earCenter;
rayEarLeft.direction = Vector3.left * distanceCheck;
// Find both the left and right ear from the ear center in the right and left directions
for (int v = 0; v < verticies.Length; v++)
{
for (var c = eyeDistance / 2; c < distanceCheck; c += 0.001f)
{
var pointEarRight = rayEarRight.GetPoint(c);
var pointEarLeft = rayEarLeft.GetPoint(c);
var pointVert = verticies[v];
var def = 0.09f;
var offsetR = pointVert - pointEarRight;
// TODO: Need to fix the left offset because of negative numbers
var offsetL = pointVert - pointEarLeft;
var lenR = offsetR.sqrMagnitude;
var lenL = offsetL.sqrMagnitude;
if (lenR < def * def || lenR < def)
{
earRightPos = pointEarRight;
}
if (lenL < def * def || lenL < def)
{
earLeftPos = pointEarLeft;
}
}
}
var earLeftCheck = Vector3.Distance(earLeftPos, earCenter);
var earRightCheck = Vector3.Distance(earRightPos, earCenter);
var eyeCenterDistance = eyeDistance + 0.01f;
if (earLeftCheck > eyeCenterDistance)
{
earLeftPos.x = (-eyeDistance);
}
if (earRightCheck > eyeCenterDistance)
{
earRightPos.x = eyeDistance;
}
if(drawRays)
{
DebugDrawRays(30f, distanceCheck, rightEye, leftEye, head, rayRightEye, rayLeftEye, faceCenter, earCenter);
}
return CreateNewCharacterPrefab(selection, nosePos, earRightPos, earLeftPos, keypointTemplate, savePath);
}
/// <summary>
/// Drays the rays used to create the nose and ears
/// </summary>
/// <param name="duration">Duration of the ray being drawn</param>
/// <param name="distanceCheck">How far the ray goes</param>
/// <param name="rightEye">transform of the right eye</param>
/// <param name="leftEye">transform of the left eye</param>
/// <param name="head">transform of the head</param>
/// <param name="rayRightEye">ray from the right eye transform</param>
/// <param name="rayLeftEye">ray from the left eye transform</param>
/// <param name="faceCenter">center of face found from the eyes</param>
/// <param name="earCenter">center of eyes found from the center of the head</param>
public static void DebugDrawRays(float duration, float distanceCheck, Transform rightEye, Transform leftEye, Transform head,Ray rayRightEye, Ray rayLeftEye, Vector3 faceCenter, Vector3 earCenter)
{
Debug.DrawLine(rightEye.position, leftEye.position, Color.magenta, duration);
Debug.DrawRay(rayLeftEye.origin, rayLeftEye.direction, Color.green, duration);
Debug.DrawRay(rayRightEye.origin, rayRightEye.direction, Color.blue, duration);
Debug.DrawRay(faceCenter, Vector3.forward * distanceCheck, Color.cyan, duration);
Debug.DrawRay(faceCenter, Vector3.back, Color.cyan, duration);
Debug.DrawRay(head.position, Vector3.up, Color.red, duration);
Debug.DrawRay(earCenter, Vector3.right, Color.blue, duration);
Debug.DrawRay(earCenter, Vector3.left, Color.green, duration);
}
/// <summary>
/// Grabs the positions for the nose and ears then creates the joint, adds labels, parents the joints to the head, then saves the prefab
/// </summary>
/// <param name="selection">target character</param>
/// <param name="nosePosition">Vector 3 position</param>
/// <param name="earRightPosition">Vector 3 position</param>
/// <param name="earLeftPosition">Vector 3 position</param>
/// <param name="savePath">Save path for the creation of a new prefab</param>
/// <returns></returns>
public static GameObject CreateNewCharacterPrefab(GameObject selection, Vector3 nosePosition, Vector3 earRightPosition, Vector3 earLeftPosition, Object keypointTemplate, string savePath = "Assets/")
{
var head = FindBodyPart("head", selection.transform);
if (savePath == string.Empty)
savePath = "Assets/";
var filePath = savePath + selection.name + ".prefab";
if (head != null)
{
if (nosePosition != Vector3.zero)
{
var nose = new GameObject();
nose.transform.position = nosePosition;
nose.name = "nose";
nose.transform.SetParent(head);
AddJointLabel(nose, keypointTemplate);
}
if (earRightPosition != Vector3.zero)
{
var earRight = new GameObject();
earRight.transform.position = earRightPosition;
earRight.name = "earRight";
earRight.transform.SetParent(head);
AddJointLabel(earRight, keypointTemplate);
}
if (earLeftPosition != Vector3.zero)
{
var earLeft = new GameObject();
earLeft.transform.position = earLeftPosition;
earLeft.name = "earLeft";
earLeft.transform.SetParent(head);
AddJointLabel(earLeft, keypointTemplate);
}
}
var model = PrefabUtility.SaveAsPrefabAsset(selection, filePath);
return model;
}
static Transform FindBodyPart(string name, Transform root)
{
var children = new List<Transform>();
root.GetComponentsInChildren(children);
foreach (var child in children)
{
if (child.name == name || child.name.Contains("head"))
return child;
}
return null;
}
/// <summary>
/// Add a joint label and add the template data for the joint, uses base CocoKeypointTemplate in perception since the plan is to
/// remove the template from the template data
/// </summary>
/// <param name="gameObject">target gameobject from the joint</param>
/// <param name="keypointTemplate">selected keypoint template from the UI, if blank it will grab the example CocoKeypointTemplate</param>
static void AddJointLabel(GameObject gameObject, Object keypointTemplate)
{
var jointLabel = gameObject.AddComponent<JointLabel>();
var data = new JointLabel.TemplateData();
var exampleTemplate = AssetDatabase.GetAllAssetPaths().Where(o => o.EndsWith("CocoKeypointTemplate.asset", StringComparison.OrdinalIgnoreCase)).ToList();
if (keypointTemplate == null)
{
foreach (string o in exampleTemplate)
{
if (o.Contains("com.unity.perception"))
{
keypointTemplate = AssetDatabase.LoadAssetAtPath<Object>(o);
}
}
}
data.label = gameObject.name;
data.template = (KeypointTemplate)keypointTemplate;
jointLabel.templateInformation = new List<JointLabel.TemplateData>();
jointLabel.templateInformation.Add(data);
}
}
}