Skip to content

Commit

Permalink
refactor eyebonelimits
Browse files Browse the repository at this point in the history
  • Loading branch information
emperorofmars committed Dec 11, 2024
1 parent 5b624b8 commit 85da641
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 56 deletions.
74 changes: 44 additions & 30 deletions AVA/Common/Editor/AVA_EyeTrackingBoneLimits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,26 @@ namespace nna.ava.common
{
public static class EyeTrackingBoneLimits
{
private class EyeTrackingBoneLimitsResult
{
public Vector4 Result;
}

public const string _Type = "ava.eyetracking_bone_limits";
public const string MatchExpression = @"(?i)\$EyeBoneLimits(?<up>[0-9]*[.][0-9]+),(?<down>[0-9]*[.][0-9]+),(?<in>[0-9]*[.][0-9]+),(?<out>[0-9]*[.][0-9]+)(?<side>([._\-|:][lr])|[._\-|:\s]?(right|left))?$";

public static (Vector4 LimitsLeft, Vector4 LimitsRight) ParseGlobal(NNAContext Context)
public static void ParseJsonToMessage(NNAContext Context, JObject Json)
{
(var limitsLeft, var limitsRight) = ParseJson(Json);
Context.AddMessage(_Type + ".limits_left", limitsLeft);
Context.AddMessage(_Type + ".limits_right", limitsRight);
}

public static void ParseNameDefinitionToMessage(NNAContext Context, string NameDefinition)
{
var Json = Context.GetJsonComponentByNode(Context.Root.transform, _Type);
if(Json != null) return ParseJson(Json);
else return ParseNameGlobal(Context);
(Vector4 Limits, bool LeftMatch, bool RightMatch) = ParseNameDefinition(NameDefinition);
if(LeftMatch) Context.AddMessage(_Type + ".limits_left", Limits);
if(RightMatch) Context.AddMessage(_Type + ".limits_right", Limits);
}

public static (Vector4 LimitsLeft, Vector4 LimitsRight) ParseJson(JObject Json)
Expand All @@ -48,37 +60,39 @@ public static (Vector4 LimitsLeft, Vector4 LimitsRight) ParseJson(JObject Json)
return (limitsLeft, limitsRight);
}

public static (Vector4 LimitsLeft, Vector4 LimitsRight) ParseNameGlobal(NNAContext Context)
public static (Vector4 Limits, bool LeftMatch, bool RightMatch) ParseNameDefinition(string NameDefinition)
{
var limits = new Vector4(15.0f, 12.0f, 15.0f, 16.0f);
bool leftMatch = false;
bool rightMatch = false;

var match = Regex.Match(NameDefinition, MatchExpression);

if(ParseUtil.MatchSymmetrySide(NameDefinition) < 1) leftMatch = true;
if(ParseUtil.MatchSymmetrySide(NameDefinition) > -1) rightMatch = true;

limits.x = match.Groups["up"].Success ? float.Parse(match.Groups["up"].Value) : 15.0f;
limits.y = match.Groups["down"].Success ? float.Parse(match.Groups["down"].Value) : 12.0f;
limits.z = match.Groups["in"].Success ? float.Parse(match.Groups["in"].Value) : 15.0f;
limits.w = match.Groups["out"].Success ? float.Parse(match.Groups["out"].Value) : 16.0f;

return (limits, leftMatch, rightMatch);
}

public static bool LimitsExplicitelyDefined(NNAContext Context)
{
return Context.HasMessage(_Type + ".limits_left") && Context.HasMessage(_Type + ".limits_right");
}

public static (Vector4 LimitsLeft, Vector4 LimitsRight) GetLimitsOrDefault(NNAContext Context)
{
var limitsLeft = new Vector4(15.0f, 12.0f, 15.0f, 16.0f);
var limitsRight = new Vector4(15.0f, 12.0f, 15.0f, 16.0f);

// This is a bit stupid.
// TODO create a system for processors to set information for another procesor.
// That way the a name processor could do this when matched in NNAConverter, and store the information in the NNAContext for this global processor.
foreach(var t in Context.Root.GetComponentsInChildren<Transform>())
if(LimitsExplicitelyDefined(Context))
{
if(Regex.IsMatch(t.name, MatchExpression))
{
var match = Regex.Match(t.name, MatchExpression);
if(ParseUtil.MatchSymmetrySide(t.name) < 1)
{
limitsLeft.x = match.Groups["up"].Success ? float.Parse(match.Groups["up"].Value) : 15.0f;
limitsLeft.y = match.Groups["down"].Success ? float.Parse(match.Groups["down"].Value) : 12.0f;
limitsLeft.z = match.Groups["in"].Success ? float.Parse(match.Groups["in"].Value) : 15.0f;
limitsLeft.w = match.Groups["out"].Success ? float.Parse(match.Groups["out"].Value) : 16.0f;
}
if(ParseUtil.MatchSymmetrySide(t.name) > -1)
{
limitsRight.x = match.Groups["up"].Success ? float.Parse(match.Groups["up"].Value) : 15.0f;
limitsRight.y = match.Groups["down"].Success ? float.Parse(match.Groups["down"].Value) : 12.0f;
limitsRight.z = match.Groups["in"].Success ? float.Parse(match.Groups["in"].Value) : 15.0f;
limitsRight.w = match.Groups["out"].Success ? float.Parse(match.Groups["out"].Value) : 16.0f;
}

if(Context.ImportOptions.RemoveNNADefinitions && match.Length == t.name.Length) Context.AddTrash(t);
if(t.name.Contains("$$")) t.name = t.name[..t.name.IndexOf('$')];
}
limitsLeft = Context.GetMessage<Vector4>(_Type + ".limits_left");
limitsRight = Context.GetMessage<Vector4>(_Type + ".limits_right");
}
return (limitsLeft, limitsRight);
}
Expand Down
56 changes: 47 additions & 9 deletions AVA/UNIVRM0/Editor/Processors/AVA_EyeTrackingBoneLimits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#if NNA_AVA_UNIVRM0_FOUND

using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
using nna.ava.common;
using nna.processors;
using UnityEditor;
Expand All @@ -10,7 +12,39 @@

namespace nna.ava.univrm0
{
public class AVAEyeTrackingBoneLimits_UNIVRM0Processor : IGlobalProcessor
public class AVA_EyeTrackingBoneLimits_UNIVRM0_JsonProcessor : IJsonProcessor
{
public const string _Type = EyeTrackingBoneLimits._Type;
public string Type => _Type;
public uint Order => 0;

public int Priority => int.MaxValue;

public void Process(NNAContext Context, Transform Node, JObject Json)
{
EyeTrackingBoneLimits.ParseJsonToMessage(Context, Json);
}
}

public class AVA_EyeTrackingBoneLimits_UNIVRM0_NameProcessor : INameProcessor
{
public const string _Type = EyeTrackingBoneLimits._Type;
public string Type => _Type;
public uint Order => 0;

public int CanProcessName(NNAContext Context, string NameDefinition)
{
var match = Regex.Match(NameDefinition, EyeTrackingBoneLimits.MatchExpression);
return match.Success ? match.Index : -1;
}

public void Process(NNAContext Context, Transform Node, string NameDefinition)
{
EyeTrackingBoneLimits.ParseNameDefinitionToMessage(Context, NameDefinition);
}
}

public class AVAEyeTrackingBoneLimits_UNIVRM0_Processor : IGlobalProcessor
{
public const string _Type = EyeTrackingBoneLimits._Type;
public string Type => _Type;
Expand All @@ -19,19 +53,21 @@ public class AVAEyeTrackingBoneLimits_UNIVRM0Processor : IGlobalProcessor
public void Process(NNAContext Context)
{
var avatarJson = Context.GetOnlyJsonComponentByType("ava.avatar").Component;
if(avatarJson != null && avatarJson.ContainsKey("auto") && !(bool)avatarJson["auto"]) return;

var Json = Context.GetJsonComponentOrDefault(Context.Root.transform, _Type);
if(avatarJson != null
&& avatarJson.ContainsKey("auto")
&& !(bool)avatarJson["auto"]
&& !EyeTrackingBoneLimits.LimitsExplicitelyDefined(Context)
) return; // No automapping otherwise

var avatar = Context.Root.GetComponent<VRMMeta>();
if(!avatar) throw new NNAException("No Avatar Component created!", _Type);
var animator = Context.Root.GetComponent<Animator>();
if(!animator) throw new NNAException("No Animator found!", _Type);

// set eyebones if human
if(animator.isHuman)
{
(var limitsLeft, var limitsRight) = EyeTrackingBoneLimits.ParseGlobal(Context);
(var limitsLeft, var limitsRight) = EyeTrackingBoneLimits.GetLimitsOrDefault(Context);
Setup(Context, avatar, animator, limitsLeft, limitsRight);
}
else
Expand All @@ -44,7 +80,7 @@ public static void Setup(NNAContext Context, VRMMeta Avatar, Animator AnimatorHu
{
var humanEyeL = AnimatorHumanoid.avatar.humanDescription.human.FirstOrDefault(hb => hb.humanName == HumanBodyBones.LeftEye.ToString());
var humanEyeR = AnimatorHumanoid.avatar.humanDescription.human.FirstOrDefault(hb => hb.humanName == HumanBodyBones.RightEye.ToString());

if(humanEyeL.boneName != null && humanEyeR.boneName != null)
{
var eyeL = Context.Root.GetComponentsInChildren<Transform>().FirstOrDefault(t => t.name == humanEyeL.boneName);
Expand All @@ -70,10 +106,12 @@ public class Register_AVAEyeTrackingBoneLimits_UNIVRM0
{
static Register_AVAEyeTrackingBoneLimits_UNIVRM0()
{
NNARegistry.RegisterGlobalProcessor(new AVAEyeTrackingBoneLimits_UNIVRM0Processor(), DetectorUNIVRM0.NNA_UNIVRM0_CONTEXT, true);
NNARegistry.RegisterGlobalProcessor(new AVAEyeTrackingBoneLimits_UNIVRM0_Processor(), DetectorUNIVRM0.NNA_UNIVRM0_CONTEXT, false);
NNARegistry.RegisterJsonProcessor(new AVA_EyeTrackingBoneLimits_UNIVRM0_JsonProcessor(), DetectorUNIVRM0.NNA_UNIVRM0_CONTEXT);
NNARegistry.RegisterNameProcessor(new AVA_EyeTrackingBoneLimits_UNIVRM0_NameProcessor(), DetectorUNIVRM0.NNA_UNIVRM0_CONTEXT);
}
}
}

#endif
#endif
#endif
64 changes: 54 additions & 10 deletions AVA/VRChat/Editor/Processors/AVA_EyeTrackingBoneLimits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
using nna.ava.common;
using nna.processors;
Expand All @@ -13,16 +14,52 @@

namespace nna.ava.vrchat
{
public class AVA_EyeTrackingBoneLimits_VRC_JsonProcessor : IJsonProcessor
{
public const string _Type = EyeTrackingBoneLimits._Type;
public string Type => _Type;
public uint Order => 0;

public int Priority => int.MaxValue;

public void Process(NNAContext Context, Transform Node, JObject Json)
{
EyeTrackingBoneLimits.ParseJsonToMessage(Context, Json);
}
}

public class AVA_EyeTrackingBoneLimits_VRC_NameProcessor : INameProcessor
{
public const string _Type = EyeTrackingBoneLimits._Type;
public string Type => _Type;
public uint Order => 0;

public int CanProcessName(NNAContext Context, string NameDefinition)
{
var match = Regex.Match(NameDefinition, EyeTrackingBoneLimits.MatchExpression);
return match.Success ? match.Index : -1;
}

public void Process(NNAContext Context, Transform Node, string NameDefinition)
{
EyeTrackingBoneLimits.ParseNameDefinitionToMessage(Context, NameDefinition);
}
}

public class AVA_EyeTrackingBoneLimits_VRC_Processor : IGlobalProcessor
{
public const string _Type = EyeTrackingBoneLimits._Type;
public string Type => _Type;
public uint Order => 2;
public uint Order => AVA_Avatar_VRCProcessor._Order + 1;

public void Process(NNAContext Context)
{
var avatarJson = Context.GetOnlyJsonComponentByType("ava.avatar").Component;
if(avatarJson != null && avatarJson.ContainsKey("auto") && !(bool)avatarJson["auto"]) return;
if(avatarJson != null
&& avatarJson.ContainsKey("auto")
&& !(bool)avatarJson["auto"]
&& !EyeTrackingBoneLimits.LimitsExplicitelyDefined(Context)
) return; // No automapping otherwise

var avatar = Context.Root.GetComponent<VRCAvatarDescriptor>();
if(!avatar) throw new NNAException("No Avatar Component created!", _Type);
Expand All @@ -32,14 +69,15 @@ public void Process(NNAContext Context)
// set eyebones if human
if(animator.isHuman)
{
(var limitsLeft, var limitsRight) = EyeTrackingBoneLimits.ParseGlobal(Context);
VRCEyeTrackingBoneLimits.Setup(Context, avatar, animator, limitsLeft, limitsRight);
(var limitsLeft, var limitsRight) = EyeTrackingBoneLimits.GetLimitsOrDefault(Context);
Setup(Context, avatar, animator, limitsLeft, limitsRight);
}
else
{
throw new NNAException("Animator is not 'Humanoid'!", _Type);
}
}
}

public static class VRCEyeTrackingBoneLimits
{
public static void Setup(NNAContext Context, VRCAvatarDescriptor Avatar, Animator AnimatorHumanoid, Vector4 LimitsLeft, Vector4 LimitsRight)
{
var humanEyeL = AnimatorHumanoid.avatar.humanDescription.human.FirstOrDefault(hb => hb.humanName == HumanBodyBones.LeftEye.ToString());
Expand All @@ -66,6 +104,7 @@ public static void Setup(NNAContext Context, VRCAvatarDescriptor Avatar, Animato
}
}


public class AVA_EyeTrackingBoneLimits_VRC_Serializer : INNASerializer
{
public static readonly System.Type _Target = typeof(VRCAvatarDescriptor);
Expand All @@ -76,7 +115,7 @@ public List<SerializerResult> Serialize(NNASerializerContext Context, UnityEngin
var avatar = (VRCAvatarDescriptor)UnityObject;
if(avatar.enableEyeLook == true)
{
var retJson = new JObject {{"t", AVA_EyeTrackingBoneLimits_VRC_Processor._Type}};
var retJson = new JObject {{"t", EyeTrackingBoneLimits._Type}};

var linkedUpDown = FixAngle(-avatar.customEyeLookSettings.eyesLookingUp.left.eulerAngles.x) == FixAngle(-avatar.customEyeLookSettings.eyesLookingUp.right.eulerAngles.x)
&& FixAngle(avatar.customEyeLookSettings.eyesLookingDown.left.eulerAngles.x) == FixAngle(avatar.customEyeLookSettings.eyesLookingDown.right.eulerAngles.x);
Expand All @@ -95,6 +134,8 @@ public List<SerializerResult> Serialize(NNASerializerContext Context, UnityEngin
retJson.Add("right_in", FixAngle(-avatar.customEyeLookSettings.eyesLookingLeft.right.eulerAngles.y));
retJson.Add("right_out", FixAngle(avatar.customEyeLookSettings.eyesLookingRight.right.eulerAngles.y));
}


string retName = null;
if(linkedUpDown && linkedLeftRight)
{
Expand All @@ -104,8 +145,9 @@ public List<SerializerResult> Serialize(NNASerializerContext Context, UnityEngin
+ "," + FixAngle(avatar.customEyeLookSettings.eyesLookingRight.left.eulerAngles.y)
+ "," + FixAngle(-avatar.customEyeLookSettings.eyesLookingLeft.left.eulerAngles.y);
}

return new List<SerializerResult>{new(){
NNAType = AVA_EyeTrackingBoneLimits_VRC_Processor._Type,
NNAType = EyeTrackingBoneLimits._Type,
Origin = UnityObject,
JsonTargetNode = "$root",
JsonResult = retJson.ToString(Newtonsoft.Json.Formatting.None),
Expand Down Expand Up @@ -137,7 +179,9 @@ public class Register_AVA_EyeTrackingBoneLimits_VRC
{
static Register_AVA_EyeTrackingBoneLimits_VRC()
{
NNARegistry.RegisterGlobalProcessor(new AVA_EyeTrackingBoneLimits_VRC_Processor(), DetectorVRC.NNA_VRC_AVATAR_CONTEXT, true);
NNARegistry.RegisterGlobalProcessor(new AVA_EyeTrackingBoneLimits_VRC_Processor(), DetectorVRC.NNA_VRC_AVATAR_CONTEXT, false);
NNARegistry.RegisterJsonProcessor(new AVA_EyeTrackingBoneLimits_VRC_JsonProcessor(), DetectorVRC.NNA_VRC_AVATAR_CONTEXT);
NNARegistry.RegisterNameProcessor(new AVA_EyeTrackingBoneLimits_VRC_NameProcessor(), DetectorVRC.NNA_VRC_AVATAR_CONTEXT);
NNAExportRegistry.RegisterSerializer(new AVA_EyeTrackingBoneLimits_VRC_Serializer());
}
}
Expand Down
26 changes: 19 additions & 7 deletions NNA/Runtime/NNAContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public class NNAContext

private readonly Dictionary<string, List<object>> ResultsById = new();

private readonly Dictionary<string, object> Messages = new();

public NNAContext(
GameObject Root,
NNAImportOptions ImportOptions,
Expand Down Expand Up @@ -56,6 +58,23 @@ public List<object> GetResultsById(string Id) {
return ResultsById.GetValueOrDefault(Id);
}

public void AddMessage(string Id, object Message) {
if(!string.IsNullOrWhiteSpace(Id))
{
if(Messages.ContainsKey(Id)) ResultsById[Id].Add(Message);
else Messages.Add(Id, Message);
}
}
public bool HasMessage(string Id) {
return Messages.ContainsKey(Id);
}
public T GetMessage<T>(string Id) {
return (T)Messages[Id];
}
public object GetMessage(string Id) {
return Messages[Id];
}

public (JObject Component, Transform Node) GetJsonComponentById(string Id) { return ImportState.JsonComponentsById.GetValueOrDefault(Id); }
public Object GetNameComponentById(string Id) { return ImportState.NameComponentsById.GetValueOrDefault(Id); }
public List<(JObject Component, Transform Node)> GetJsonComponentByType(string Type) { return ImportState.JsonComponentsByType.GetValueOrDefault(Type, new List<(JObject Component, Transform Node)>()); }
Expand Down Expand Up @@ -85,13 +104,6 @@ public string GetMetaCustomValue(string Key) {
return ImportState.Meta.CustomProperties.FirstOrDefault(e => e.Key == Key)?.Value;
}

public void SetNodeName(Transform Node, string NewName)
{
this.AddTask(new Task(() => {
Node.name = NewName;
}));
}

public void AddTask(Task Task) { ImportState.Tasks.Add(Task); }
public void AddTrash(Transform Trash) { ImportState.Trash.Add(Trash); }
public void AddTrash(IEnumerable<Transform> Trash) { ImportState.Trash.AddRange(Trash); }
Expand Down
3 changes: 3 additions & 0 deletions NNA/Runtime/NNAConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ public static ImportResult Convert(
{
State.RegisterNameComponent(node, selectedProcessor.Type);
var nameDefinition = node.name;

if(State.ImportOptions.RemoveNNADefinitions && node.name.StartsWith("$")) Context.AddTrash(node);
node.name = ParseUtil.GetNodeNameCleaned(node.name);

State.AddProcessorTask(selectedProcessor.Order, new Task(() => {
selectedProcessor.Process(Context, node, nameDefinition);
}));
Expand Down

0 comments on commit 85da641

Please sign in to comment.