Merge pull request #714 from Santarh/expressionImpl3

Implements expression overriding
This commit is contained in:
ousttrue
2021-02-02 15:03:51 +09:00
committed by GitHub
15 changed files with 214 additions and 352 deletions

View File

@@ -136,6 +136,9 @@ namespace UniVRM10
}
serializedObject.Update();
// Setup runtime function.
m_target.Setup();
// base.OnInspectorGUI();
switch (_tab)
@@ -176,14 +179,6 @@ namespace UniVRM10
void ExpressionGUI()
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Override rates", EditorStyles.boldLabel);
EditorGUI.BeginDisabledGroup(true);
{
EditorGUILayout.Slider("Blink override rate", m_target.Expression.BlinkOverrideRate, 0f, 1f);
EditorGUILayout.Slider("LookAt override rate", m_target.Expression.LookAtOverrideRate, 0f, 1f);
EditorGUILayout.Slider("Mouth override rate", m_target.Expression.MouthOverrideRate, 0f, 1f);
}
EditorGUI.EndDisabledGroup();
if (!Application.isPlaying)
{
@@ -197,6 +192,9 @@ namespace UniVRM10
if (m_sliders != null)
{
EditorGUILayout.Space();
EditorGUILayout.LabelField("Expression Weights", EditorStyles.boldLabel);
var sliders = m_sliders.Select(x => x.Slider());
foreach (var slider in sliders)
{
@@ -204,6 +202,16 @@ namespace UniVRM10
}
m_target.Expression.SetWeights(m_expressionKeyWeights);
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Override rates", EditorStyles.boldLabel);
EditorGUI.BeginDisabledGroup(true);
{
EditorGUILayout.Slider("Blink override rate", m_target.Expression.BlinkOverrideRate, 0f, 1f);
EditorGUILayout.Slider("LookAt override rate", m_target.Expression.LookAtOverrideRate, 0f, 1f);
EditorGUILayout.Slider("Mouth override rate", m_target.Expression.MouthOverrideRate, 0f, 1f);
}
EditorGUI.EndDisabledGroup();
}
void OnSceneGUI()

View File

@@ -1,37 +1,102 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using VrmLib;
namespace UniVRM10
{
public sealed class DefaultExpressionValidator : IExpressionValidator
{
private readonly ExpressionKey[] _keys;
private readonly Dictionary<ExpressionKey, VRM10Expression> _expressions;
private DefaultExpressionValidator(VRM10ExpressionAvatar expressionAvatar)
{
_keys = expressionAvatar.Clips.Select(ExpressionKey.CreateFromClip).ToArray();
_expressions = expressionAvatar.Clips.ToDictionary(ExpressionKey.CreateFromClip, x => x);
}
public void Validate(IReadOnlyDictionary<ExpressionKey, float> inputWeights, IDictionary<ExpressionKey, float> actualWeights,
LookAtEyeDirection inputEyeDirection, out LookAtEyeDirection actualEyeDirection,
out float blinkOverrideRate, out float lookAtOverrideRate, out float mouthOverrideRate)
{
// weights
foreach (var (key, weight) in inputWeights)
{
if (!actualWeights.ContainsKey(key))
{
actualWeights.Add(key, weight);
}
actualWeights[key] = weight;
}
// eye direction
actualEyeDirection = inputEyeDirection;
// override rate
blinkOverrideRate = 0f;
lookAtOverrideRate = 0f;
mouthOverrideRate = 0f;
// 1. Set weights and Accumulate override rates.
foreach (var key in _keys)
{
// Get expression.
if (!_expressions.ContainsKey(key)) continue;
var expression = _expressions[key];
// Get weight with evaluation binary flag.
var weight = expression.IsBinary ? Mathf.Round(inputWeights[key]) : inputWeights[key];
// Set weight.
actualWeights[key] = weight;
// Override rate without targeting myself.
if (!key.IsBlink)
{
blinkOverrideRate += GetOverrideRate(expression.OverrideBlink, weight);
}
if (!key.IsLookAt)
{
lookAtOverrideRate += GetOverrideRate(expression.OverrideLookAt, weight);
}
if (!key.IsMouth)
{
mouthOverrideRate += GetOverrideRate(expression.OverrideMouth, weight);
}
}
// 2. Saturate rate.
blinkOverrideRate = Mathf.Clamp01(blinkOverrideRate);
lookAtOverrideRate = Mathf.Clamp01(lookAtOverrideRate);
mouthOverrideRate = Mathf.Clamp01(mouthOverrideRate);
var blinkMultiplier = 1f - blinkOverrideRate;
var lookAtMultiplier = 1f - lookAtOverrideRate;
var mouthMultiplier = 1f - mouthOverrideRate;
// 3. Set procedural key's weights.
foreach (var key in _keys)
{
if (key.IsBlink)
{
actualWeights[key] = inputWeights[key] * blinkMultiplier;
}
else if (key.IsLookAt)
{
actualWeights[key] = inputWeights[key] * lookAtMultiplier;
}
else if (key.IsMouth)
{
actualWeights[key] = inputWeights[key] * mouthMultiplier;
}
}
// 4. eye direction
actualEyeDirection = LookAtEyeDirection.Multiply(inputEyeDirection, 1f - lookAtOverrideRate);
}
private float GetOverrideRate(ExpressionOverrideType type, float weight)
{
switch (type)
{
case ExpressionOverrideType.None:
return 0f;
case ExpressionOverrideType.Block:
return weight > 0f ? 1f : 0f;
case ExpressionOverrideType.Blend:
return weight;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
public sealed class Factory : IExpressionValidatorFactory

View File

@@ -1,150 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace UniVRM10
{
public class DefaultExpressionAccumulator : IExpressionAccumulator
{
Dictionary<ExpressionKey, VRM10Expression> m_clipMap;
Dictionary<ExpressionKey, float> expressionKeyWeights = new Dictionary<ExpressionKey, float>();
public bool IgnoreBlink { get; private set; }
public bool IgnoreLookAt { get; private set; }
public bool IgnoreMouth { get; private set; }
/// <summary>
/// initilaize
/// </summary>
/// <param name="avatar"></param>
public void OnStart(VRM10ExpressionAvatar avatar)
{
m_clipMap = avatar.Clips.ToDictionary(x => ExpressionKey.CreateFromClip(x), x => x);
expressionKeyWeights = m_clipMap.Keys.ToDictionary(x => x, x => 0.0f);
}
/// <summary>
/// each frame
/// </summary>
/// <returns></returns>
public IEnumerable<KeyValuePair<ExpressionKey, float>> FrameExpression()
{
IgnoreBlink = false;
IgnoreLookAt = false;
IgnoreMouth = false;
//
// except blink, lookat, mouth
//
foreach (var kv in expressionKeyWeights)
{
if (kv.Key.IsProcedual)
{
// 後で
continue;
}
var expression = m_clipMap[kv.Key];
if (expression.OverrideBlink == VrmLib.ExpressionOverrideType.Block && kv.Value > 0)
{
IgnoreBlink = true;
}
if (expression.OverrideLookAt == VrmLib.ExpressionOverrideType.Block && kv.Value > 0)
{
IgnoreLookAt = true;
}
if (expression.OverrideMouth == VrmLib.ExpressionOverrideType.Block && kv.Value > 0)
{
IgnoreMouth = true;
}
yield return kv;
}
//
// blink, lookat, mouth
//
foreach (var kv in expressionKeyWeights)
{
if (kv.Key.IsBlink)
{
if (IgnoreBlink)
{
// skip
continue;
}
}
else if (kv.Key.IsLookAt)
{
if (IgnoreLookAt)
{
// skip
continue;
}
}
else if (kv.Key.IsMouth)
{
if (IgnoreMouth)
{
// skip
continue;
}
}
else
{
// already return
continue;
}
yield return kv;
}
}
/// <summary>
/// Get a expression value
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public float GetValue(ExpressionKey key)
{
if (expressionKeyWeights.ContainsKey(key))
{
return expressionKeyWeights[key];
}
else
{
return 0.0f;
}
}
public IEnumerable<KeyValuePair<ExpressionKey, float>> GetValues()
{
return expressionKeyWeights.Select(x => new KeyValuePair<ExpressionKey, float>(x.Key, x.Value));
}
/// <summary>
/// SetValue
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void SetValue(ExpressionKey key, float value)
{
if (expressionKeyWeights.ContainsKey(key))
{
expressionKeyWeights[key] = value;
}
}
public void SetValues(IEnumerable<KeyValuePair<ExpressionKey, float>> values)
{
foreach (var keyValue in values)
{
if (expressionKeyWeights.ContainsKey(keyValue.Key))
{
expressionKeyWeights[keyValue.Key] = keyValue.Value;
}
}
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: bcb5bbef7ed54854d8041988a84fa827
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -4,28 +4,34 @@ using System.Collections.Generic;
namespace UniVRM10
{
[Serializable]
public struct ExpressionKey : IEquatable<ExpressionKey>, IComparable<ExpressionKey>
public readonly struct ExpressionKey : IEquatable<ExpressionKey>, IComparable<ExpressionKey>
{
/// <summary>
/// Enum.ToString() のGC回避用キャッシュ
/// </summary>
private static readonly Dictionary<VrmLib.ExpressionPreset, string> m_presetNameDictionary =
private static readonly Dictionary<VrmLib.ExpressionPreset, string> PresetNameDictionary =
new Dictionary<VrmLib.ExpressionPreset, string>();
/// <summary>
/// ExpressionPreset と同名の名前を持つ独自に追加した Expression を区別するための prefix
/// </summary>
private static readonly string UnknownPresetPrefix = "Unknown_";
private string m_customName;
public string Name
{
get { return m_customName.ToUpper(); }
}
public VrmLib.ExpressionPreset Preset;
/// <summary>
/// Preset of this ExpressionKey.
/// </summary>
public readonly VrmLib.ExpressionPreset Preset;
/// <summary>
/// Custom Name of this ExpressionKey.
/// This works if Preset was Custom.
/// </summary>
public readonly string Name;
/// <summary>
/// Id for comparison of ExpressionKey.
/// </summary>
private readonly string _id;
public bool IsBlink
{
@@ -77,61 +83,31 @@ namespace UniVRM10
public bool IsProcedual => IsBlink || IsLookAt || IsMouth;
string m_id;
string ID
{
get
{
if (string.IsNullOrEmpty(m_id))
{
// Unknown was deleted
if (Preset != VrmLib.ExpressionPreset.Custom)
{
if (m_presetNameDictionary.ContainsKey(Preset))
{
m_id = m_presetNameDictionary[Preset];
}
else
{
m_presetNameDictionary.Add(Preset, Preset.ToString());
m_id = m_presetNameDictionary[Preset];
}
}
else
{
m_id = UnknownPresetPrefix + m_customName;
}
}
return m_id;
}
}
public ExpressionKey(VrmLib.ExpressionPreset preset, string customName = null)
{
Preset = preset;
m_customName = customName;
if (Preset != VrmLib.ExpressionPreset.Custom)
{
if (m_presetNameDictionary.ContainsKey((Preset)))
if (PresetNameDictionary.ContainsKey((Preset)))
{
m_id = m_presetNameDictionary[Preset];
_id = Name = PresetNameDictionary[Preset];
}
else
{
m_presetNameDictionary.Add(Preset, Preset.ToString());
m_id = m_presetNameDictionary[Preset];
PresetNameDictionary.Add(Preset, Preset.ToString());
_id = Name = PresetNameDictionary[Preset];
}
}
else
{
if (string.IsNullOrEmpty(m_customName))
if (string.IsNullOrEmpty(customName))
{
throw new ArgumentException("name is required for VrmLib.ExpressionPreset.Custom");
}
m_id = UnknownPresetPrefix + m_customName;
_id = $"{UnknownPresetPrefix}{customName}";
Name = customName;
}
}
@@ -157,12 +133,12 @@ namespace UniVRM10
public override string ToString()
{
return ID.Replace(UnknownPresetPrefix, "").ToUpper();
return _id.Replace(UnknownPresetPrefix, "");
}
public bool Equals(ExpressionKey other)
{
return ID == other.ID;
return _id == other._id;
}
public override bool Equals(object obj)
@@ -179,7 +155,7 @@ namespace UniVRM10
public override int GetHashCode()
{
return ID.GetHashCode();
return _id.GetHashCode();
}
public bool Match(VRM10Expression clip)

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using VrmLib;
namespace UniVRM10
@@ -38,12 +39,12 @@ namespace UniVRM10
/// <summary>
/// まとめて反映する。1フレームに1回呼び出されることを想定
/// </summary>
/// <param name="values"></param>
public void SetValues(IEnumerable<KeyValuePair<ExpressionKey, float>> values)
/// <param name="expressionWeights"></param>
public void SetValues(Dictionary<ExpressionKey, float> expressionWeights)
{
foreach (var kv in values)
foreach (var (key, weight) in expressionWeights)
{
AccumulateValue(kv.Key, kv.Value);
AccumulateValue(key, weight);
}
m_morphTargetBindingMerger.Apply();
@@ -60,11 +61,6 @@ namespace UniVRM10
return;
}
if (clip.IsBinary)
{
value = Mathf.Round(value);
}
m_morphTargetBindingMerger.AccumulateValue(clip, value);
m_materialValueBindingMerger.AccumulateValue(clip, value);
}

View File

@@ -1,58 +0,0 @@
using System.Collections.Generic;
namespace UniVRM10
{
/// <summary>
/// 1フレーム分の Expression を蓄える
/// </summary>
public interface IExpressionAccumulator
{
/// <summary>
/// 開始時に初期化する
/// </summary>
/// <param name="avatar"></param>
void OnStart(VRM10ExpressionAvatar avatar);
/// <summary>
/// 蓄えて処理(ignoreフラグなど)した結果を得る
/// </summary>
/// <returns></returns>
IEnumerable<KeyValuePair<ExpressionKey, float>> FrameExpression();
void SetValue(ExpressionKey key, float value);
void SetValues(IEnumerable<KeyValuePair<ExpressionKey, float>> values);
float GetValue(ExpressionKey key);
IEnumerable<KeyValuePair<ExpressionKey, float>> GetValues();
bool IgnoreBlink { get; }
bool IgnoreLookAt { get; }
bool IgnoreMouth { get; }
}
public static class ExpressionAccumulatorExtensions
{
public static float GetPresetValue(this IExpressionAccumulator self, VrmLib.ExpressionPreset key)
{
var expressionKey = new ExpressionKey(key);
return self.GetValue(expressionKey);
}
public static float GetCustomValue(this IExpressionAccumulator self, string key)
{
var expressionKey = ExpressionKey.CreateCustom(key);
return self.GetValue(expressionKey);
}
public static void SetPresetValue(this IExpressionAccumulator self, VrmLib.ExpressionPreset key, float value)
{
var expressionKey = new ExpressionKey(key);
self.SetValue(expressionKey, value);
}
public static void SetCustomValue(this IExpressionAccumulator self, string key, float value)
{
var expressionKey = ExpressionKey.CreateCustom(key);
self.SetValue(expressionKey, value);
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: fdd7cdf6d59574348bb7336272ef5edf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -7,6 +7,7 @@ namespace UniVRM10
/// </summary>
internal interface ILookAtEyeDirectionApplicable
{
IEnumerable<KeyValuePair<ExpressionKey, float>> Apply(LookAtEyeDirection eyeDirection);
void Apply(LookAtEyeDirection eyeDirection, Dictionary<ExpressionKey, float> actualWeights);
void Restore();
}
}

View File

@@ -2,9 +2,24 @@
{
public readonly struct LookAtEyeDirection
{
/// <summary>
/// Yaw of LeftEye
/// </summary>
public float LeftYaw { get; }
/// <summary>
/// Pitch of LeftEye
/// </summary>
public float LeftPitch { get; }
/// <summary>
/// Yaw of RightEye
/// </summary>
public float RightYaw { get; }
/// <summary>
/// Pitch of RightEye
/// </summary>
public float RightPitch { get; }
public LookAtEyeDirection(float leftYaw, float leftPitch, float rightYaw, float rightPitch)
@@ -14,5 +29,15 @@
RightYaw = rightYaw;
RightPitch = rightPitch;
}
public static LookAtEyeDirection Multiply(LookAtEyeDirection a, float b)
{
return new LookAtEyeDirection(
a.LeftYaw * b,
a.LeftPitch * b,
a.RightYaw * b,
a.RightPitch * b
);
}
}
}

View File

@@ -26,8 +26,9 @@ namespace UniVRM10
/// <summary>
/// LeftEyeボーンとRightEyeボーンに回転を適用する
/// </summary>
public IEnumerable<KeyValuePair<ExpressionKey, float>> Apply(LookAtEyeDirection eyeDirection)
public void Apply(LookAtEyeDirection eyeDirection, Dictionary<ExpressionKey, float> actualWeights)
{
// FIXME
var yaw = eyeDirection.LeftYaw;
var pitch = eyeDirection.LeftPitch;
@@ -55,14 +56,21 @@ namespace UniVRM10
}
// Apply
SetYawPitchToBones(new LookAtEyeDirection(leftYaw, pitch, rightYaw, pitch));
}
public void Restore()
{
SetYawPitchToBones(new LookAtEyeDirection(0, 0, 0, 0));
}
private void SetYawPitchToBones(LookAtEyeDirection actualEyeDirection)
{
if (_leftEye != null && _rightEye != null)
{
// 目に値を適用する
_leftEye.localRotation = Matrix4x4.identity.YawPitchRotation(leftYaw, pitch);
_rightEye.localRotation = Matrix4x4.identity.YawPitchRotation(rightYaw, pitch);
_leftEye.localRotation = Matrix4x4.identity.YawPitchRotation(actualEyeDirection.LeftYaw, actualEyeDirection.LeftPitch);
_rightEye.localRotation = Matrix4x4.identity.YawPitchRotation(actualEyeDirection.RightYaw, actualEyeDirection.RightPitch);
}
yield break;
}
}
}

View File

@@ -25,7 +25,7 @@ namespace UniVRM10
_verticalUp = verticalUp;
}
public IEnumerable<KeyValuePair<ExpressionKey, float>> Apply(LookAtEyeDirection eyeDirection)
public void Apply(LookAtEyeDirection eyeDirection, Dictionary<ExpressionKey, float> actualWeights)
{
var yaw = eyeDirection.LeftYaw;
var pitch = eyeDirection.LeftPitch;
@@ -33,28 +33,32 @@ namespace UniVRM10
if (yaw < 0)
{
// Left
yield return new KeyValuePair<ExpressionKey, float>(_lookRightKey, 0);
yield return new KeyValuePair<ExpressionKey, float>(_lookLeftKey, Mathf.Clamp(_horizontalOuter.Map(Mathf.Abs(yaw)), 0, 1.0f));
actualWeights[_lookRightKey] = 0;
actualWeights[_lookLeftKey] = Mathf.Clamp(_horizontalOuter.Map(Mathf.Abs(yaw)), 0, 1.0f);
}
else
{
// Right
yield return new KeyValuePair<ExpressionKey, float>(_lookRightKey, Mathf.Clamp(_horizontalOuter.Map(Mathf.Abs(yaw)), 0, 1.0f));
yield return new KeyValuePair<ExpressionKey, float>(_lookLeftKey, 0);
actualWeights[_lookRightKey] = Mathf.Clamp(_horizontalOuter.Map(Mathf.Abs(yaw)), 0, 1.0f);
actualWeights[_lookLeftKey] = 0;
}
if (pitch < 0)
{
// Down
yield return new KeyValuePair<ExpressionKey, float>(_lookUpKey, 0);
yield return new KeyValuePair<ExpressionKey, float>(_lookDownKey, Mathf.Clamp(_verticalDown.Map(Mathf.Abs(pitch)), 0, 1.0f));
actualWeights[_lookUpKey] = 0;
actualWeights[_lookDownKey] = Mathf.Clamp(_verticalDown.Map(Mathf.Abs(pitch)), 0, 1.0f);
}
else
{
// Up
yield return new KeyValuePair<ExpressionKey, float>(_lookUpKey, Mathf.Clamp(_verticalUp.Map(Mathf.Abs(pitch)), 0, 1.0f));
yield return new KeyValuePair<ExpressionKey, float>(_lookDownKey, 0);
actualWeights[_lookUpKey] = Mathf.Clamp(_verticalUp.Map(Mathf.Abs(pitch)), 0, 1.0f);
actualWeights[_lookDownKey] = 0;
}
}
public void Restore()
{
}
}
}

View File

@@ -52,7 +52,10 @@ namespace UniVRM10
void OnDestroy()
{
Expression.Dispose();
if (Expression != null)
{
Expression.Restore();
}
if (ModelAsset != null)
{

View File

@@ -7,7 +7,7 @@ using VrmLib;
namespace UniVRM10
{
[Serializable]
public sealed class VRM10ControllerExpression : IDisposable
public sealed class VRM10ControllerExpression
{
public static IExpressionValidatorFactory ExpressionValidatorFactory = new DefaultExpressionValidator.Factory();
@@ -31,11 +31,6 @@ namespace UniVRM10
public float LookAtOverrideRate { get; private set; }
public float MouthOverrideRate { get; private set; }
public void Dispose()
{
_merger?.RestoreMaterialInitialValues();
}
internal void Setup(Transform transform, ILookAtEyeDirectionProvider eyeDirectionProvider, ILookAtEyeDirectionApplicable eyeDirectionApplicable)
{
if (ExpressionAvatar == null)
@@ -44,14 +39,31 @@ namespace UniVRM10
return;
}
Restore();
_merger = new ExpressionMerger(ExpressionAvatar.Clips, transform);
_keys = ExpressionAvatar.Clips.Select(ExpressionKey.CreateFromClip).ToList();
var oldInputWeights = _inputWeights;
_inputWeights = _keys.ToDictionary(x => x, x => 0f);
foreach (var key in _keys)
{
// remain user input weights.
if (oldInputWeights.ContainsKey(key)) _inputWeights[key] = oldInputWeights[key];
}
_actualWeights = _keys.ToDictionary(x => x, x => 0f);
_validator = ExpressionValidatorFactory.Create(ExpressionAvatar);
_eyeDirectionProvider = eyeDirectionProvider;
_eyeDirectionApplicable = eyeDirectionApplicable;
}
internal void Restore()
{
_merger?.RestoreMaterialInitialValues();
_merger = null;
_eyeDirectionApplicable?.Restore();
_eyeDirectionApplicable = null;
}
public void Process()
{
@@ -106,7 +118,7 @@ namespace UniVRM10
private void Apply()
{
// 1. Get eye direction from provider.
_inputEyeDirection = _eyeDirectionProvider.EyeDirection;
_inputEyeDirection = _eyeDirectionProvider?.EyeDirection ?? default;
// 2. Validate user input, and Output as actual weights.
_validator.Validate(_inputWeights, _actualWeights,
@@ -114,18 +126,7 @@ namespace UniVRM10
out var blink, out var lookAt, out var mouth);
// 3. Set eye direction expression weights or any other side-effects (ex. eye bone).
if (_eyeDirectionApplicable != null)
{
foreach (var (expressionKey, weight) in _eyeDirectionApplicable.Apply(_actualEyeDirection))
{
if (!_actualWeights.ContainsKey(expressionKey))
{
_actualWeights.Add(expressionKey, 0f);
}
_actualWeights[expressionKey] = weight;
}
}
_eyeDirectionApplicable?.Apply(_actualEyeDirection, _actualWeights);
// 4. Set actual weights to raw blendshapes.
_merger.SetValues(_actualWeights);

View File

@@ -136,7 +136,12 @@ namespace UniVRM10
m_head = head;
m_leftEye = animator.GetBoneTransform(HumanBodyBones.LeftEye);
m_rightEye = animator.GetBoneTransform(HumanBodyBones.RightEye);
if (Gaze == null)
var isRuntimeAsset = true;
#if UNITY_EDITOR
isRuntimeAsset = Application.isPlaying && !PrefabUtility.IsPartOfAnyPrefab(m_head);
#endif
if (isRuntimeAsset && LookAtTargetType == LookAtTargetTypes.CalcYawPitchToGaze && Gaze == null)
{
Gaze = new GameObject().transform;
Gaze.name = "__LOOKAT_GAZE__";