habeebweeb 4 rokov pred
rodič
commit
1bfdb81b99

+ 8 - 3
COM3D2.MeidoPhotoStudio.Plugin/Config/MeidoPhotoStudio/Translations/en/translation.ui.json

@@ -111,9 +111,14 @@
         "shiftPanties": "Shift",
         "detail": "Detailed Clothing"
     },
-    "hands": {
-        "rightHand": "R Hand",
-        "leftHand": "L Hand"
+    "handPane": {
+        "saveToggle": "Save Hand",
+        "categoryHeader": "Category",
+        "nameHeader": "Name",
+        "saveLeftButton": "Left",
+        "saveRightButton": "Right",
+        "rightHand": "Right",
+        "leftHand": "Left"
     },
     "copyPosePane": {
         "copyButton": "Copy"

+ 93 - 1
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Constants.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Xml.Linq;
 using Newtonsoft.Json;
 using MyRoomCustom;
 using UnityEngine;
@@ -14,11 +15,13 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
     {
         private static bool beginHandItemInit;
         public const string customPoseDirectory = "Custom Poses";
+        public const string customHandDirectory = "Hand Presets";
         public const string sceneDirectory = "Scenes";
         public const string kankyoDirectory = "Environments";
         public const string configDirectory = "MeidoPhotoStudio";
         public const string translationDirectory = "Translations";
         public static readonly string customPosePath;
+        public static readonly string customHandPath;
         public static readonly string scenesPath;
         public static readonly string kankyoPath;
         public static readonly string configPath;
@@ -39,6 +42,8 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public static readonly Dictionary<string, List<string>> PoseDict = new Dictionary<string, List<string>>();
         public static readonly List<string> CustomPoseGroupList = new List<string>();
         public static readonly Dictionary<string, List<string>> CustomPoseDict = new Dictionary<string, List<string>>();
+        public static readonly List<string> CustomHandGroupList = new List<string>();
+        public static readonly Dictionary<string, List<string>> CustomHandDict = new Dictionary<string, List<string>>();
         public static readonly List<string> FaceBlendList = new List<string>();
         public static readonly List<string> BGList = new List<string>();
         public static readonly List<KeyValuePair<string, string>> MyRoomCustomBGList
@@ -56,6 +61,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public static bool MenuFilesInitialized { get; private set; } = false;
         public static event EventHandler<MenuFilesEventArgs> MenuFilesChange;
         public static event EventHandler<CustomPoseEventArgs> customPoseChange;
+        public static event EventHandler<CustomPoseEventArgs> customHandChange;
         public enum DoguCategory
         {
             Other, Mob, Desk, HandItem, BGSmall
@@ -75,11 +81,14 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             string modsPath = Path.Combine(BepInEx.Paths.GameRootPath, @"Mod\MeidoPhotoStudio");
 
             customPosePath = Path.Combine(modsPath, customPoseDirectory);
+            customHandPath = Path.Combine(modsPath, customHandDirectory);
             scenesPath = Path.Combine(modsPath, sceneDirectory);
             kankyoPath = Path.Combine(modsPath, kankyoDirectory);
             configPath = Path.Combine(BepInEx.Paths.ConfigPath, configDirectory);
 
-            foreach (string directory in new[] { customPosePath, scenesPath, kankyoPath, configPath })
+            string[] directories = new[] { customPosePath, customHandPath, scenesPath, kankyoPath, configPath };
+
+            foreach (string directory in directories)
             {
                 if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
             }
@@ -88,6 +97,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         public static void Initialize()
         {
             InitializePoses();
+            InitializeHandPresets();
             InitializeFaceBlends();
             InitializeBGs();
             InitializeDogu();
@@ -143,6 +153,62 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             customPoseChange?.Invoke(null, new CustomPoseEventArgs(fullPath, category));
         }
 
+        public static void AddHand(byte[] handBinary, bool right, string filename, string directory)
+        {
+            filename = Utility.SanitizePathPortion(filename);
+            directory = Utility.SanitizePathPortion(directory);
+            if (string.IsNullOrEmpty(filename)) filename = "custom_hand";
+            if (directory.Equals(Constants.customHandDirectory, StringComparison.InvariantCultureIgnoreCase))
+            {
+                directory = String.Empty;
+            }
+            directory = Path.Combine(Constants.customHandPath, directory);
+
+            if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
+
+            string fullPath = Path.Combine(directory, filename);
+
+            if (File.Exists($"{fullPath}.xml")) fullPath += $"_{DateTime.Now:yyyyMMddHHmmss}";
+
+            fullPath += ".xml";
+
+            if (!fullPath.StartsWith(Constants.customHandPath))
+            {
+                Utility.Logger.LogError($"Could not save hand! Path is invalid: '{fullPath}'");
+                return;
+            }
+
+            XDocument finalXml = new XDocument(new XDeclaration("1.0", "utf-8", "true"),
+                new XComment("CM3D2 FingerData"),
+                new XElement("FingerData",
+                    new XElement("GameVersion", Misc.GAME_VERSION),
+                    new XElement("RightData", right),
+                    new XElement("BinaryData", Convert.ToBase64String(handBinary))
+                )
+            );
+
+            finalXml.Save(fullPath);
+
+            FileInfo fileInfo = new FileInfo(fullPath);
+
+            string category = fileInfo.Directory.Name;
+            string handGroup = Constants.CustomHandGroupList.Find(
+                group => string.Equals(category, group, StringComparison.InvariantCultureIgnoreCase)
+            );
+
+            if (string.IsNullOrEmpty(handGroup))
+            {
+                Constants.CustomHandGroupList.Add(category);
+                Constants.CustomHandDict[category] = new List<string>();
+            }
+            else category = handGroup;
+
+            Constants.CustomHandDict[category].Add(fullPath);
+            Constants.CustomHandDict[category].Sort();
+
+            customHandChange?.Invoke(null, new CustomPoseEventArgs(fullPath, category));
+        }
+
         public static void InitializePoses()
         {
             // Load Poses
@@ -227,6 +293,32 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             }
         }
 
+        public static void InitializeHandPresets()
+        {
+            Action<string> GetPresets = directory =>
+            {
+                IEnumerable<string> presetList = Directory.GetFiles(directory)
+                    .Where(file => Path.GetExtension(file) == ".xml");
+
+                if (presetList.Count() > 0)
+                {
+                    string presetCategory = new DirectoryInfo(directory).Name;
+                    if (presetCategory != customHandDirectory) CustomHandGroupList.Add(presetCategory);
+                    CustomHandDict[presetCategory] = new List<string>(presetList);
+                }
+            };
+
+            CustomHandGroupList.Add(customHandDirectory);
+            CustomHandDict[customHandDirectory] = new List<string>();
+
+            GetPresets(customHandPath);
+
+            foreach (string directory in Directory.GetDirectories(customHandPath))
+            {
+                GetPresets(directory);
+            }
+        }
+
         public static void InitializeFaceBlends()
         {
             using (CsvParser csvParser = OpenCsvParser("phot_face_list.nei"))

+ 28 - 13
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Panes/MainWindowPanes/PoseWindowPane.cs

@@ -15,20 +15,24 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
         private MaidFaceLookPane maidFaceLookPane;
         private MaidDressingPane maidDressingPane;
         private CopyPosePane copyPosePane;
+        private HandPresetPane handPresetPane;
+        private SaveHandPane saveHandPane;
         private MaidIKPane maidIKPane;
         private Toggle freeLookToggle;
         private Toggle savePoseToggle;
-
+        private Toggle saveHandToggle;
         private bool savePoseMode = false;
+        private bool saveHandMode = false;
 
         public PoseWindowPane(MeidoManager meidoManager, MaidSwitcherPane maidSwitcherPane)
         {
             this.meidoManager = meidoManager;
             this.maidSwitcherPane = maidSwitcherPane;
 
-            this.savePosePane = new SavePosePane(meidoManager);
-            this.maidPosePane = new MaidPoseSelectorPane(meidoManager);
-            this.maidFaceLookPane = new MaidFaceLookPane(meidoManager);
+            this.maidPosePane = AddPane(new MaidPoseSelectorPane(meidoManager));
+            this.savePosePane = AddPane(new SavePosePane(meidoManager));
+
+            this.maidFaceLookPane = AddPane(new MaidFaceLookPane(meidoManager));
             this.maidFaceLookPane.Enabled = false;
 
             this.freeLookToggle = new Toggle(Translation.Get("freeLook", "freeLookToggle"), false);
@@ -37,17 +41,24 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             this.savePoseToggle = new Toggle(Translation.Get("posePane", "saveToggle"));
             this.savePoseToggle.ControlEvent += (s, a) => savePoseMode = !savePoseMode;
 
-            this.maidDressingPane = new MaidDressingPane(meidoManager);
+            this.maidDressingPane = AddPane(new MaidDressingPane(meidoManager));
+
+            this.maidIKPane = AddPane(new MaidIKPane(meidoManager));
 
-            this.maidIKPane = new MaidIKPane(meidoManager);
+            this.copyPosePane = AddPane(new CopyPosePane(meidoManager));
 
-            this.copyPosePane = new CopyPosePane(meidoManager);
+            this.saveHandToggle = new Toggle(Translation.Get("handPane", "saveToggle"));
+            this.saveHandToggle.ControlEvent += (s, a) => saveHandMode = !saveHandMode;
+
+            this.handPresetPane = AddPane(new HandPresetPane(meidoManager));
+            this.saveHandPane = AddPane(new SaveHandPane(meidoManager));
         }
 
         protected override void ReloadTranslation()
         {
             this.freeLookToggle.Label = Translation.Get("freeLook", "freeLookToggle");
             this.savePoseToggle.Label = Translation.Get("posePane", "saveToggle");
+            this.saveHandToggle.Label = Translation.Get("handPane", "saveToggle");
         }
 
         public override void Draw()
@@ -57,11 +68,12 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
             this.scrollPos = GUILayout.BeginScrollView(this.scrollPos);
 
-            GUILayout.BeginHorizontal();
             GUI.enabled = this.meidoManager.HasActiveMeido;
+            GUILayout.BeginHorizontal();
             freeLookToggle.Draw();
             savePoseToggle.Draw();
             GUILayout.EndHorizontal();
+            GUI.enabled = true;
 
             if (savePoseMode) savePosePane.Draw();
             else maidFaceLookPane.Draw();
@@ -72,6 +84,13 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
 
             maidIKPane.Draw();
 
+            GUI.enabled = this.meidoManager.HasActiveMeido;
+            saveHandToggle.Draw();
+            GUI.enabled = true;
+
+            if (saveHandMode) saveHandPane.Draw();
+            else handPresetPane.Draw();
+
             copyPosePane.Draw();
 
             GUILayout.EndScrollView();
@@ -92,11 +111,7 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
                 this.updating = true;
                 this.freeLookToggle.Value = this.meidoManager.ActiveMeido?.IsFreeLook ?? false;
                 this.updating = false;
-                maidPosePane.UpdatePane();
-                maidFaceLookPane.UpdatePane();
-                maidDressingPane.UpdatePane();
-                maidIKPane.UpdatePane();
-                copyPosePane.UpdatePane();
+                base.UpdatePanes();
             }
         }
 

+ 125 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Panes/PoseWindowPanes/HandPresetPane.cs

@@ -0,0 +1,125 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    internal class HandPresetPane : BasePane
+    {
+        private MeidoManager meidoManager;
+        private Dropdown presetCategoryDropdown;
+        private Button nextCategoryButton;
+        private Button previousCategoryButton;
+        private Dropdown presetDropdown;
+        private Button nextPresetButton;
+        private Button previousPresetButton;
+        private Button leftHandButton;
+        private Button rightHandButton;
+        private string SelectedCategory => Constants.CustomHandGroupList[presetCategoryDropdown.SelectedItemIndex];
+        private List<string> CurrentPresetList => Constants.CustomHandDict[SelectedCategory];
+        private string CurrentPreset => CurrentPresetList[presetDropdown.SelectedItemIndex];
+        private string previousCategory;
+        private bool presetListEnabled = true;
+
+        public HandPresetPane(MeidoManager meidoManager)
+        {
+            Constants.customHandChange += SaveHandEnd;
+            this.meidoManager = meidoManager;
+
+            this.presetCategoryDropdown = new Dropdown(Constants.CustomHandGroupList.ToArray());
+            this.presetCategoryDropdown.SelectionChange += (s, a) => ChangePresetCategory();
+
+            this.nextCategoryButton = new Button(">");
+            this.nextCategoryButton.ControlEvent += (s, a) => this.presetCategoryDropdown.Step(1);
+
+            this.previousCategoryButton = new Button("<");
+            this.previousCategoryButton.ControlEvent += (s, a) =>
+            {
+                this.presetCategoryDropdown.Step(-1);
+            };
+
+            this.presetDropdown = new Dropdown(UIPresetList());
+
+            this.nextPresetButton = new Button(">");
+            this.nextPresetButton.ControlEvent += (s, a) => this.presetDropdown.Step(1);
+
+            this.previousPresetButton = new Button("<");
+            this.previousPresetButton.ControlEvent += (s, a) => this.presetDropdown.Step(-1);
+
+            this.leftHandButton = new Button("Left");
+            this.leftHandButton.ControlEvent += (s, a) => SetHandPreset(right: false);
+
+            this.rightHandButton = new Button("Right");
+            this.rightHandButton.ControlEvent += (s, a) => SetHandPreset(right: true);
+
+            this.previousCategory = SelectedCategory;
+            this.presetListEnabled = CurrentPresetList.Count > 0;
+        }
+
+        public override void Draw()
+        {
+            GUILayoutOption dropdownWidth = GUILayout.Width(156f);
+            GUILayoutOption noExpandWidth = GUILayout.ExpandWidth(false);
+
+            GUI.enabled = this.meidoManager.HasActiveMeido;
+
+            GUILayout.BeginHorizontal();
+            this.presetCategoryDropdown.Draw(dropdownWidth);
+            this.previousCategoryButton.Draw(noExpandWidth);
+            this.nextCategoryButton.Draw(noExpandWidth);
+            GUILayout.EndHorizontal();
+
+            GUI.enabled = GUI.enabled && presetListEnabled;
+
+            GUILayout.BeginHorizontal();
+            this.presetDropdown.Draw(dropdownWidth);
+            this.previousPresetButton.Draw(noExpandWidth);
+            this.nextPresetButton.Draw(noExpandWidth);
+            GUILayout.EndHorizontal();
+
+            GUILayout.BeginHorizontal();
+            this.rightHandButton.Draw();
+            this.leftHandButton.Draw();
+            GUILayout.EndHorizontal();
+
+            GUI.enabled = true;
+        }
+
+        private void ChangePresetCategory()
+        {
+            presetListEnabled = CurrentPresetList.Count > 0;
+            if (previousCategory == SelectedCategory)
+            {
+                this.presetDropdown.SelectedItemIndex = 0;
+            }
+            else
+            {
+                previousCategory = SelectedCategory;
+                this.presetDropdown.SetDropdownItems(UIPresetList(), 0);
+            }
+        }
+
+        private void SetHandPreset(bool right = false)
+        {
+            if (!meidoManager.HasActiveMeido) return;
+
+            this.meidoManager.ActiveMeido.SetHandPreset(CurrentPreset, right);
+        }
+
+        private void SaveHandEnd(object sender, CustomPoseEventArgs args)
+        {
+            this.presetCategoryDropdown.SetDropdownItems(
+                Constants.CustomHandGroupList.ToArray(), Constants.CustomHandGroupList.IndexOf(args.Category)
+            );
+            this.presetDropdown.SetDropdownItems(UIPresetList(), CurrentPresetList.IndexOf(args.Path));
+        }
+
+        private string[] UIPresetList()
+        {
+            if (CurrentPresetList.Count == 0) return new[] { "No Hand Presets" };
+            else return CurrentPresetList.Select(file => Path.GetFileNameWithoutExtension(file)).ToArray();
+        }
+    }
+}

+ 72 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/GUI/Panes/PoseWindowPanes/SaveHandPane.cs

@@ -0,0 +1,72 @@
+using UnityEngine;
+
+namespace COM3D2.MeidoPhotoStudio.Plugin
+{
+    internal class SaveHandPane : BasePane
+    {
+        private MeidoManager meidoManager;
+        private ComboBox categoryComboBox;
+        private TextField handNameTextField;
+        private Button saveLeftHandButton;
+        private Button saveRightHandButton;
+        private string categoryHeader;
+        private string nameHeader;
+
+        public SaveHandPane(MeidoManager meidoManager)
+        {
+            Constants.customHandChange += (s, a) =>
+            {
+                this.categoryComboBox.SetDropdownItems(Constants.CustomHandGroupList.ToArray());
+            };
+
+            this.meidoManager = meidoManager;
+
+            this.categoryHeader = Translation.Get("handPane", "categoryHeader");
+
+            this.nameHeader = Translation.Get("handPane", "nameHeader");
+
+            this.saveLeftHandButton = new Button(Translation.Get("handPane", "saveLeftButton"));
+            this.saveLeftHandButton.ControlEvent += (s, a) => SaveHand(right: false);
+
+            this.saveRightHandButton = new Button(Translation.Get("handPane", "saveRightButton"));
+            this.saveRightHandButton.ControlEvent += (s, a) => SaveHand(right: true);
+
+            this.categoryComboBox = new ComboBox(Constants.CustomHandGroupList.ToArray());
+
+            this.handNameTextField = new TextField();
+        }
+
+        protected override void ReloadTranslation()
+        {
+            this.categoryHeader = Translation.Get("handPane", "categoryHeader");
+            this.nameHeader = Translation.Get("handPane", "nameHeader");
+            this.saveLeftHandButton.Label = Translation.Get("handPane", "saveLeftButton");
+            this.saveRightHandButton.Label = Translation.Get("handPane", "saveRightButton");
+        }
+
+        public override void Draw()
+        {
+            GUI.enabled = this.meidoManager.HasActiveMeido;
+
+            MiscGUI.Header(categoryHeader);
+            this.categoryComboBox.Draw(GUILayout.Width(165f));
+
+            MiscGUI.Header(nameHeader);
+            this.handNameTextField.Draw(GUILayout.Width(165f));
+
+            GUILayout.BeginHorizontal();
+            this.saveRightHandButton.Draw();
+            this.saveLeftHandButton.Draw();
+            GUILayout.EndHorizontal();
+
+            GUI.enabled = true;
+        }
+
+        private void SaveHand(bool right)
+        {
+            byte[] handBinary = this.meidoManager.ActiveMeido.SerializeHand(right);
+            Constants.AddHand(handBinary, right, this.handNameTextField.Value, this.categoryComboBox.Value);
+            this.handNameTextField.Value = string.Empty;
+        }
+    }
+}

+ 25 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Meido/Meido.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections;
 using System.IO;
+using System.Xml.Linq;
 using UnityEngine;
 
 namespace COM3D2.MeidoPhotoStudio.Plugin
@@ -114,6 +115,30 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             return cache.GetAnmBinary(true, true);
         }
 
+        public void SetHandPreset(string filename, bool right)
+        {
+            if (this.dragPointManager == null) return;
+
+            XDocument handDocument = XDocument.Load(filename);
+            XElement handElement = handDocument.Element("FingerData");
+            if (handElement.IsEmpty || handElement.Element("GameVersion").IsEmpty
+                || handElement.Element("RightData").IsEmpty || handElement.Element("BinaryData").IsEmpty)
+            {
+                return;
+            }
+
+            IsStop = true;
+
+            bool rightData = bool.Parse(handElement.Element("RightData").Value);
+            string base64Data = handElement.Element("BinaryData").Value;
+
+            byte[] handData = Convert.FromBase64String(base64Data);
+
+            this.dragPointManager.DeserializeHand(handData, right, rightData != right);
+        }
+
+        public byte[] SerializeHand(bool right) => this.dragPointManager?.SerializeHand(right);
+
         public Maid Load(int activeSlot, int maidSlot)
         {
             isLoading = true;

+ 52 - 0
COM3D2.MeidoPhotoStudio.Plugin/MeidoPhotoStudio/Meido/MeidoDragPointManager.cs

@@ -1,4 +1,5 @@
 using System;
+using System.IO;
 using System.Collections.Generic;
 using UnityEngine;
 
@@ -146,6 +147,57 @@ namespace COM3D2.MeidoPhotoStudio.Plugin
             return BoneTransform[PointToBone[point]];
         }
 
+        public byte[] SerializeHand(bool right)
+        {
+            Bone start = right ? Bone.Finger0R : Bone.Finger0L;
+            Bone end = right ? Bone.Finger4R : Bone.Finger4L;
+
+            byte[] buf;
+            using (MemoryStream memoryStream = new MemoryStream())
+            using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream))
+            {
+                for (Bone bone = start; bone <= end; bone += 4)
+                {
+                    for (int i = 0; i < 3; i++)
+                    {
+                        Quaternion localRotation = BoneTransform[bone + i].localRotation;
+                        binaryWriter.Write(localRotation.x);
+                        binaryWriter.Write(localRotation.y);
+                        binaryWriter.Write(localRotation.z);
+                        binaryWriter.Write(localRotation.w);
+                    }
+                }
+                buf = memoryStream.ToArray();
+            }
+            return buf;
+        }
+
+        public void DeserializeHand(byte[] handBinary, bool right, bool mirroring = false)
+        {
+            Bone start = right ? Bone.Finger0R : Bone.Finger0L;
+            Bone end = right ? Bone.Finger4R : Bone.Finger4L;
+
+            int mirror = mirroring ? -1 : 1;
+
+            using (MemoryStream memoryStream = new MemoryStream(handBinary))
+            using (BinaryReader binaryReader = new BinaryReader(memoryStream))
+            {
+                for (Bone bone = start; bone <= end; bone += 4)
+                {
+                    for (int i = 0; i < 3; i++)
+                    {
+                        Vector4 vec4;
+                        vec4.x = binaryReader.ReadSingle() * mirror;
+                        vec4.y = binaryReader.ReadSingle() * mirror;
+                        vec4.z = binaryReader.ReadSingle();
+                        vec4.w = binaryReader.ReadSingle();
+
+                        BoneTransform[bone + i].localRotation = new Quaternion(vec4.x, vec4.y, vec4.z, vec4.w);
+                    }
+                }
+            }
+        }
+
         public void Destroy()
         {
             foreach (DragPointMeido dragPoint in DragPoints.Values)