diff --git a/AVA/Common/Editor/AVA_EyeTrackingBoneLimits.cs b/AVA/Common/Editor/AVA_EyeTrackingBoneLimits.cs index 909e375..9a22f79 100644 --- a/AVA/Common/Editor/AVA_EyeTrackingBoneLimits.cs +++ b/AVA/Common/Editor/AVA_EyeTrackingBoneLimits.cs @@ -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(?[0-9]*[.][0-9]+),(?[0-9]*[.][0-9]+),(?[0-9]*[.][0-9]+),(?[0-9]*[.][0-9]+)(?([._\-|:][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) @@ -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()) + 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(_Type + ".limits_left"); + limitsRight = Context.GetMessage(_Type + ".limits_right"); } return (limitsLeft, limitsRight); } diff --git a/AVA/UNIVRM0/Editor/Processors/AVA_EyeTrackingBoneLimits.cs b/AVA/UNIVRM0/Editor/Processors/AVA_EyeTrackingBoneLimits.cs index 17cf541..bde8584 100644 --- a/AVA/UNIVRM0/Editor/Processors/AVA_EyeTrackingBoneLimits.cs +++ b/AVA/UNIVRM0/Editor/Processors/AVA_EyeTrackingBoneLimits.cs @@ -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; @@ -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; @@ -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(); if(!avatar) throw new NNAException("No Avatar Component created!", _Type); var animator = Context.Root.GetComponent(); 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 @@ -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().FirstOrDefault(t => t.name == humanEyeL.boneName); @@ -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 \ No newline at end of file +#endif diff --git a/AVA/VRChat/Editor/Processors/AVA_EyeTrackingBoneLimits.cs b/AVA/VRChat/Editor/Processors/AVA_EyeTrackingBoneLimits.cs index b5a1305..9f047b2 100644 --- a/AVA/VRChat/Editor/Processors/AVA_EyeTrackingBoneLimits.cs +++ b/AVA/VRChat/Editor/Processors/AVA_EyeTrackingBoneLimits.cs @@ -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; @@ -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(); if(!avatar) throw new NNAException("No Avatar Component created!", _Type); @@ -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()); @@ -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); @@ -76,7 +115,7 @@ public List 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); @@ -95,6 +134,8 @@ public List 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) { @@ -104,8 +145,9 @@ public List Serialize(NNASerializerContext Context, UnityEngin + "," + FixAngle(avatar.customEyeLookSettings.eyesLookingRight.left.eulerAngles.y) + "," + FixAngle(-avatar.customEyeLookSettings.eyesLookingLeft.left.eulerAngles.y); } + return new List{new(){ - NNAType = AVA_EyeTrackingBoneLimits_VRC_Processor._Type, + NNAType = EyeTrackingBoneLimits._Type, Origin = UnityObject, JsonTargetNode = "$root", JsonResult = retJson.ToString(Newtonsoft.Json.Formatting.None), @@ -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()); } } diff --git a/NNA/Runtime/NNAContext.cs b/NNA/Runtime/NNAContext.cs index 194388f..89568cf 100644 --- a/NNA/Runtime/NNAContext.cs +++ b/NNA/Runtime/NNAContext.cs @@ -25,6 +25,8 @@ public class NNAContext private readonly Dictionary> ResultsById = new(); + private readonly Dictionary Messages = new(); + public NNAContext( GameObject Root, NNAImportOptions ImportOptions, @@ -56,6 +58,23 @@ public List 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(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)>()); } @@ -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 Trash) { ImportState.Trash.AddRange(Trash); } diff --git a/NNA/Runtime/NNAConverter.cs b/NNA/Runtime/NNAConverter.cs index 5f1e83a..0e26bce 100644 --- a/NNA/Runtime/NNAConverter.cs +++ b/NNA/Runtime/NNAConverter.cs @@ -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); }));