diff --git a/CHANGELOG.md b/CHANGELOG.md index 03c05a0..750c3a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Changelog ========= +### 1.6.0 +- Fixes to root bones and physics components in LOD combiner. +- Support for Magica Cloth 2 Physics (if present) - Cloth simulation and spring bones. +- Collider Manager rebuild to support magica cloth + - Collider editor now uses direct on screen manipulation of colliders. +- Support for Json Schema 1.2 + - A bug in CC4 can cause the character to export with this schema. +- Run time wrinkle material fix. + ### 1.5.2 - Animation Retargeter fixes. - AnimationMode removed. diff --git a/Editor/AnimPlayerGUI.cs b/Editor/AnimPlayerGUI.cs index c0cba55..4eff47d 100644 --- a/Editor/AnimPlayerGUI.cs +++ b/Editor/AnimPlayerGUI.cs @@ -49,7 +49,7 @@ public static void OpenPlayer(GameObject scenePrefab) { if (scenePrefab) { - scenePrefab = Util.TryResetScenePrefab(scenePrefab); + //scenePrefab = Util.TryResetScenePrefab(scenePrefab); SetCharacter(scenePrefab); } @@ -84,11 +84,11 @@ public static void ClosePlayer() EditorApplication.update -= UpdateCallback; EditorApplication.playModeStateChanged -= PlayStateChangeCallback; - if (CharacterAnimator) - { - GameObject scenePrefab = Util.GetScenePrefabInstanceRoot(CharacterAnimator.gameObject); - Util.TryResetScenePrefab(scenePrefab); - } + //if (CharacterAnimator) + ///{ + //GameObject scenePrefab = Util.GetScenePrefabInstanceRoot(CharacterAnimator.gameObject); + //Util.TryResetScenePrefab(scenePrefab); + //} #if SCENEVIEW_OVERLAY_COMPATIBLE //2021.2.0a17+ @@ -121,7 +121,7 @@ public static bool IsPlayerShown() public static void SetCharacter(GameObject scenePrefab) { if (scenePrefab) - Util.LogInfo("scenePrefab.name: " + scenePrefab.name + " " + PrefabUtility.IsPartOfPrefabInstance(scenePrefab)); + Util.LogDetail("scenePrefab.name: " + scenePrefab.name + " " + PrefabUtility.IsPartOfPrefabInstance(scenePrefab)); if (!scenePrefab && WindowManager.IsPreviewScene) scenePrefab = WindowManager.GetPreviewScene().GetPreviewCharacter(); @@ -246,12 +246,10 @@ enum AnimatorFlags // GUIStyles private static Styles guiStyles; - [SerializeField] - private static List boneItemList; - [SerializeField] - public static bool isTracking = false; - [SerializeField] - private static GameObject lastTracked; + [SerializeField] private static List boneItemList; + [SerializeField] public static bool isTracking = false; + [SerializeField] public static GameObject lastTracked; + [SerializeField] public static bool trackingPermitted = true; private static string boneNotFound = "not found"; // ---------------------------------------------------------------------------- @@ -286,7 +284,7 @@ private static AnimatorController CreateAnimatiorController() { controllerPath = dirString + controllerName + ".controller"; - Util.LogInfo("Creating Temporary file " + controllerPath); + Util.LogDetail("Creating Temporary file " + controllerPath); AnimatorController a = AnimatorController.CreateAnimatorControllerAtPath(controllerPath); a.name = controllerName; // play mode parameters @@ -373,7 +371,7 @@ private static void SelectOverrideAnimation(AnimationClip clip, AnimatorOverride foreach (var v in overrides) { - Util.LogInfo("Overrides: " + " Key: " + v.Key + " Value: " + v.Value); + Util.LogDetail("Overrides: " + " Key: " + v.Key + " Value: " + v.Value); } overrides[0] = new KeyValuePair(overrides[0].Key, WorkingClip); @@ -390,7 +388,7 @@ public static void SelectOverrideAnimationWithoutReset(AnimationClip clip, Anima foreach (var v in overrides) { - Util.LogInfo("Overrides: " + " Key: " + v.Key + " Value: " + v.Value); + Util.LogDetail("Overrides: " + " Key: " + v.Key + " Value: " + v.Value); } overrides[0] = new KeyValuePair(overrides[0].Key, WorkingClip); @@ -439,7 +437,7 @@ public static void ResetToBaseAnimatorController() if (!characterPrefab) return; - Util.LogInfo(("Attempting to reset: " + characterPrefab.name)); + Util.LogDetail(("Attempting to reset: " + characterPrefab.name)); GameObject basePrefab = PrefabUtility.GetCorrespondingObjectFromSource(characterPrefab); @@ -448,36 +446,36 @@ public static void ResetToBaseAnimatorController() if (true) //(PrefabUtility.IsAnyPrefabInstanceRoot(basePrefab)) { string prefabPath = AssetDatabase.GetAssetPath(basePrefab); - Util.LogInfo((basePrefab.name + "Prefab instance root found: " + prefabPath)); + Util.LogDetail((basePrefab.name + "Prefab instance root found: " + prefabPath)); - Util.LogInfo("Loaded Prefab: " + basePrefab.name); + Util.LogDetail("Loaded Prefab: " + basePrefab.name); Animator baseAnimator = basePrefab.GetComponent(); if (!baseAnimator) baseAnimator = basePrefab.GetComponentInChildren(); if (baseAnimator != null) { - Util.LogInfo("Prefab Animator: " + baseAnimator.name); + Util.LogDetail("Prefab Animator: " + baseAnimator.name); if (baseAnimator.runtimeAnimatorController) { - Util.LogInfo("Prefab Animator Controller: " + baseAnimator.runtimeAnimatorController.name); + Util.LogDetail("Prefab Animator Controller: " + baseAnimator.runtimeAnimatorController.name); string controllerpath = AssetDatabase.GetAssetPath(baseAnimator.runtimeAnimatorController); - Util.LogInfo("Prefab Animator Controller Path: " + controllerpath); + Util.LogDetail("Prefab Animator Controller Path: " + controllerpath); AnimatorController baseController = AssetDatabase.LoadAssetAtPath(controllerpath); if (CharacterAnimator.runtimeAnimatorController != null) { // ensure the created override controller is the one on the animator // to avoid wiping user generated controller (it will have to be a disk asset - but nevertheless) - Util.LogInfo("Current controller on character: " + CharacterAnimator.runtimeAnimatorController.name); + Util.LogDetail("Current controller on character: " + CharacterAnimator.runtimeAnimatorController.name); if (CharacterAnimator.runtimeAnimatorController.GetType() == typeof(AnimatorOverrideController) && CharacterAnimator.runtimeAnimatorController.name == overrideName) { - Util.LogInfo("Created override controller found: can reset"); + Util.LogDetail("Created override controller found: can reset"); CharacterAnimator.runtimeAnimatorController = baseController; } } } else { - Util.LogInfo("NO Prefab Animator Controller"); + Util.LogDetail("NO Prefab Animator Controller"); CharacterAnimator.runtimeAnimatorController = null; } } @@ -495,7 +493,7 @@ private static void DestroyAnimationController() { //if (showMessages) - Util.LogInfo("Override controller: " + controllerPath + " exists -- removing"); + Util.LogDetail("Override controller: " + controllerPath + " exists -- removing"); AssetDatabase.DeleteAsset(controllerPath); } } @@ -602,6 +600,7 @@ public static void DrawPlayer() if (!CheckTackingStatus()) CancelBoneTracking(false); //object focus lost - arrange ui to reflect that, but dont fight with the scene camera + EditorGUI.BeginDisabledGroup(!trackingPermitted); if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("Camera Gizmo").image, "Select individual bone to track with the scene camera."), guiStyles.settingsButton, GUILayout.Width(24f), GUILayout.Height(24f))) { GenerateBoneMenu(); @@ -615,6 +614,7 @@ public static void DrawPlayer() CancelBoneTracking(true); //tracking deliberately cancelled - leave scene camera in last position with last tracked object still selected } EditorGUI.EndDisabledGroup(); + EditorGUI.EndDisabledGroup(); GUILayout.FlexibleSpace(); EditorGUI.BeginChangeCheck(); @@ -725,7 +725,20 @@ public static void DrawPlayer() EditorGUI.EndDisabledGroup(); GUILayout.Space(10f); - GUILayout.Label(new GUIContent(EditorGUIUtility.IconContent("d_UnityEditor.GameView").image, "Controls for 'Play Mode'"), guiStyles.playIconStyle, GUILayout.Width(24f), GUILayout.Height(24f)); + //GUILayout.Label(new GUIContent(EditorGUIUtility.IconContent("d_UnityEditor.GameView").image, "Controls for 'Play Mode'"), guiStyles.playIconStyle, GUILayout.Width(24f), GUILayout.Height(24f)); + + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("d_ViewToolOrbit On").image, "Select the character root."), EditorStyles.toolbarButton)) + { + if (ColliderManagerEditor.EditMode) + { + Selection.activeObject = null; + } + else + { + Selection.activeObject = selectedAnimator.gameObject; + } + + } Texture bigPlayButton = EditorApplication.isPlaying ? EditorGUIUtility.IconContent("preAudioPlayOn").image : EditorGUIUtility.IconContent("preAudioPlayOff").image; string playToggleTxt = EditorApplication.isPlaying ? "Exit 'Play Mode'." : "Enter 'Play Mode' and focus on the scene view window. This is to be used to evaluate play mode physics whilst allowing visualization of objects such as colliders."; @@ -1142,6 +1155,34 @@ private static void TrackBone(BoneItem boneItem) scene.Repaint(); } + public static void ForbidTracking() + { + // this is called by the collider manager editor script to use its own tracking while editing colliders + // this avoids having an objected selected since its control handles will be visible and cause problems + + if (isTracking) + { + isTracking = false; + Selection.activeObject = null; + } + + trackingPermitted = false; + + if (boneItemList != null) + { + foreach (BoneItem boneItem in boneItemList) + { + if (boneItem.selected) + boneItem.selected = false; + } + } + } + + public static void AllowTracking() + { + trackingPermitted = true; + } + public static void ReEstablishTracking(string humanBoneName) { //if (boneItemList == null) diff --git a/Editor/AnimPlayerWindow.cs b/Editor/AnimPlayerWindow.cs index cc0246a..60f471e 100644 --- a/Editor/AnimPlayerWindow.cs +++ b/Editor/AnimPlayerWindow.cs @@ -95,7 +95,7 @@ public static void DoWindow(int id) public static void Empty(int id) { - Util.LogInfo("Showing " + id); + Util.LogDetail("Showing " + id); } } } diff --git a/Editor/AnimRetargetGUI.cs b/Editor/AnimRetargetGUI.cs index 05fc3d8..d20f58e 100644 --- a/Editor/AnimRetargetGUI.cs +++ b/Editor/AnimRetargetGUI.cs @@ -470,14 +470,11 @@ public static bool CanClipLoop(AnimationClip clip) foreach (EditorCurveBinding binding in curveBindings) { Keyframe[] testKeys = AnimationUtility.GetEditorCurve(clip, binding).keys; - //Debug.Log(Math.Round(testKeys[0].value, 3) + " -- " + Math.Round(testKeys[testKeys.Length - 1].value, 3) + " --- " + (Math.Round(testKeys[0].value, 3) != Math.Round(testKeys[testKeys.Length - 1].value, 3))); if (Math.Round(testKeys[0].value, 2) != Math.Round(testKeys[testKeys.Length - 1].value, 2)) { canLoop = false; - //Debug.Log(binding.propertyName + " 0: " + testKeys[0].value + " last: " + testKeys[testKeys.Length - 1].value); } } - //Debug.Log("Animation Clip " + clip.name + (canLoop ? " can loop" : " can NOT loop")); return canLoop; } @@ -529,7 +526,6 @@ static void CloseMouthToggle(bool close) AnimPlayerGUI.UpdateAnimator(); } - static void ApplyPose(int mode) { if (!(OriginalClip && WorkingClip)) return; @@ -1325,14 +1321,11 @@ static string GenerateClipAssetPath(AnimationClip originalClip, string character return assetPath; } - - - static AnimationClip WriteAnimationToAssetDatabase(AnimationClip workingClip, string assetPath, bool originalSettings = false) { if (string.IsNullOrEmpty(assetPath)) return null; - Util.LogInfo("Writing Asset: " + assetPath); + Util.LogDetail("Writing Asset: " + assetPath); var output = Object.Instantiate(workingClip); // clone so that workingClip isn't locked to an on-disk asset AnimationClip outputClip = output as AnimationClip; @@ -1367,12 +1360,12 @@ static AnimationClip WriteAnimationToAssetDatabase(AnimationClip workingClip, st if (asset == null) { // New - Util.LogInfo("Writing New Asset: " + assetPath); + Util.LogDetail("Writing New Asset: " + assetPath); AssetDatabase.CreateAsset(outputClip, assetPath); } else { - Util.LogInfo("Updating Existing Asset: " + assetPath); + Util.LogDetail("Updating Existing Asset: " + assetPath); outputClip.name = asset.name; EditorUtility.CopySerialized(outputClip, asset); AssetDatabase.SaveAssets(); diff --git a/Editor/CharacterInfo.cs b/Editor/CharacterInfo.cs index adfd389..77e8a2f 100644 --- a/Editor/CharacterInfo.cs +++ b/Editor/CharacterInfo.cs @@ -33,11 +33,29 @@ public enum ShaderFeatureFlags NoFeatures = 0, Tessellation = 1, ClothPhysics = 2, - HairPhysics = 4, + HairPhysics = 4, SpringBoneHair = 8, WrinkleMaps = 16, + MagicaCloth = 32, + MagicaBone = 64, + UnityClothPhysics = 128, + UnityClothHairPhysics = 256 } + // 'radio groups' of mutually exclusive settings + public static ShaderFeatureFlags[] clothGroup = + { + ShaderFeatureFlags.UnityClothPhysics, // UnityEngine.Cloth instance + ShaderFeatureFlags.MagicaCloth // MagicaCloth2 instance set to 'Mesh Cloth' mode + }; + + public static ShaderFeatureFlags[] hairGroup = + { + ShaderFeatureFlags.UnityClothHairPhysics, // UnityEngine.Cloth instance for hair objects + ShaderFeatureFlags.SpringBoneHair, // DynamicBone springbones + ShaderFeatureFlags.MagicaBone // MagicaCloth2 instance set to 'Bone Cloth' mode for springbones + }; + public enum RigOverride { None = 0, Generic, Humanoid } public string guid; @@ -225,11 +243,12 @@ public void FixCharSettings() if (qualHair == HairQuality.Coverage && Pipeline.isHDRP) qualHair = HairQuality.Default; - if ((ShaderFlags & ShaderFeatureFlags.SpringBoneHair) > 0 && - (ShaderFlags & ShaderFeatureFlags.HairPhysics) > 0) - { - ShaderFlags -= ShaderFeatureFlags.SpringBoneHair; - } + //if ((ShaderFlags & ShaderFeatureFlags.SpringBoneHair) > 0 && + // (ShaderFlags & ShaderFeatureFlags.HairPhysics) > 0) + //{ + // ShaderFlags -= ShaderFeatureFlags.SpringBoneHair; + //} + CheckRadioGroupFlags(); // set default unity cloth simulation flags if unset } public CharacterInfo(string guid) @@ -249,7 +268,7 @@ public CharacterInfo(string guid) if (File.Exists(infoFilepath)) Read(); else - Write(); + Write(); } public void CopySettings(CharacterInfo from) @@ -284,7 +303,7 @@ public GameObject Fbx if (fbx == null) { fbx = AssetDatabase.LoadAssetAtPath(path); - Util.LogInfo("CharInfo: " + name + " FBX Loaded"); + Util.LogDetail("CharInfo: " + name + " FBX Loaded"); } return fbx; } @@ -375,7 +394,7 @@ public QuickJSON JsonData if (jsonData == null) { jsonData = Util.GetJsonData(jsonFilepath); - Util.LogInfo("CharInfo: " + name + " JsonData Fetched"); + Util.LogDetail("CharInfo: " + name + " JsonData Fetched"); } return jsonData; } @@ -394,6 +413,17 @@ public QuickJSON RootJsonData } } + public string JsonVersion + { + get + { + string jsonPath = name + "/Version"; + if (JsonData.PathExists(jsonPath)) + return JsonData.GetStringValue(jsonPath); + return ""; + } + } + public QuickJSON CharacterJsonData { get @@ -405,27 +435,72 @@ public QuickJSON CharacterJsonData } } - public QuickJSON MeshJsonData + public QuickJSON ObjectsJsonData { get { - string jsonPath = name + "/Object/" + name + "/Meshes"; - if (JsonData.PathExists(jsonPath)) - return JsonData.GetObjectAtPath(jsonPath); + if (JsonVersion.StartsWith("1.20.")) + { + string jsonPath = name + "/Object/" + name + "/Nodes"; + if (JsonData.PathExists(jsonPath)) + return JsonData.GetObjectAtPath(jsonPath); + } + else + { + string jsonPath = name + "/Object/" + name + "/Meshes"; + if (JsonData.PathExists(jsonPath)) + return JsonData.GetObjectAtPath(jsonPath); + } return null; } } + public string ObjectsMatJsonPath(string objName, string matName) + { + if (JsonVersion.StartsWith("1.20.")) + { + return objName + "/Meshes/" + objName + "/Materials/" + matName; + } + else + { + return objName + "/Materials/" + matName; + } + } + + public string ObjectsMaterialsJsonPath(string objName) + { + if (JsonVersion.StartsWith("1.20.")) + { + return objName + "/Meshes/" + objName + "/Materials/"; + } + else + { + return objName + "/Materials/"; + } + } + + public string ObjectsMeshJsonPath(string objName) + { + if (JsonVersion.StartsWith("1.20.")) + { + return "Nodes/" + objName + "/Meshes/" + objName; + } + else + { + return "Meshes/" + objName; + } + } + public QuickJSON GetMatJson(GameObject obj, string sourceName) { - QuickJSON jsonMeshData = MeshJsonData; + QuickJSON objectsData = ObjectsJsonData; QuickJSON matJson = null; string objName = obj.name; string jsonPath = ""; - if (jsonMeshData != null) + if (objectsData != null) { - jsonPath = objName + "/Materials/" + sourceName; - matJson = jsonMeshData.GetObjectAtPath(jsonPath); + jsonPath = ObjectsMatJsonPath(objName, sourceName); + matJson = objectsData.GetObjectAtPath(jsonPath); if (matJson == null) { @@ -433,8 +508,8 @@ public QuickJSON GetMatJson(GameObject obj, string sourceName) { objName = objName.Substring(0, objName.IndexOf("_Extracted", System.StringComparison.InvariantCultureIgnoreCase)); - jsonPath = objName + "/Materials/" + sourceName; - matJson = jsonMeshData.GetObjectAtPath(jsonPath); + jsonPath = ObjectsMatJsonPath(objName, sourceName); + matJson = objectsData.GetObjectAtPath(jsonPath); } } @@ -444,11 +519,11 @@ public QuickJSON GetMatJson(GameObject obj, string sourceName) if (objName.Contains(" ")) { Util.LogWarn("Object name " + objName + " contains a space, this can cause the materials to setup incorrectly..."); - string[] split = objName.Split(' '); - jsonPath = split[0] + "/Materials/" + sourceName; - if (jsonMeshData.PathExists(jsonPath)) + string[] split = objName.Split(' '); + jsonPath = ObjectsMatJsonPath(split[0], sourceName); + if (objectsData.PathExists(jsonPath)) { - matJson = jsonMeshData.GetObjectAtPath(jsonPath); + matJson = objectsData.GetObjectAtPath(jsonPath); Util.LogWarn(" - Found matching object/material data for: " + split[0] + "/" + sourceName); } } @@ -462,7 +537,7 @@ public QuickJSON GetMatJson(GameObject obj, string sourceName) string realObjName = null; - if (jsonMeshData.PathExists(objName)) + if (objectsData.PathExists(objName)) { realObjName = objName; } @@ -474,14 +549,14 @@ public QuickJSON GetMatJson(GameObject obj, string sourceName) { Util.LogWarn("Object name " + objName + " may be incorrectly suffixed by InstaLod exporter. Attempting to untangle..."); string specObjName = objName.Substring(0, objName.Length - 2); - if (jsonMeshData.PathExists(specObjName)) + if (objectsData.PathExists(specObjName)) { realObjName = specObjName; } else { // finally search for an object name in the mesh json whose name starts with the truncted source name - realObjName = jsonMeshData.FindKeyName(specObjName); + realObjName = objectsData.FindKeyName(specObjName); } } } @@ -490,7 +565,7 @@ public QuickJSON GetMatJson(GameObject obj, string sourceName) { string realMatName = null; - if (jsonMeshData.PathExists(realObjName + "/Materials/" + sourceName)) + if (objectsData.PathExists(ObjectsMatJsonPath(realObjName, sourceName))) { realMatName = sourceName; } @@ -501,22 +576,22 @@ public QuickJSON GetMatJson(GameObject obj, string sourceName) { Util.LogWarn("Material name " + sourceName + " may by suffixed by InstaLod exporter. Attempting to untangle..."); string specMatName = sourceName.Substring(0, sourceName.Length - 2); - if (jsonMeshData.PathExists(realObjName + "/Materials/" + specMatName)) + if (objectsData.PathExists(ObjectsMatJsonPath(realObjName, specMatName))) { realMatName = specMatName; } else { // finally search for an object name in the mesh json whose name starts with the truncted source name - realMatName = jsonMeshData.FindKeyName(realObjName + "/Materials/", specMatName); + realMatName = objectsData.FindKeyName(ObjectsMaterialsJsonPath(realObjName), specMatName); } } } if (realObjName != null && realMatName != null && - jsonMeshData.PathExists(realObjName + "/Materials/" + realMatName)) + objectsData.PathExists(ObjectsMatJsonPath(realObjName, realMatName))) { - matJson = jsonMeshData.GetObjectAtPath(realObjName + "/Materials/" + realMatName); + matJson = objectsData.GetObjectAtPath(ObjectsMatJsonPath(realObjName, realMatName)); if (matJson != null) { Util.LogWarn(" - Found matching object/material data for: " + realObjName + "/" + realMatName); @@ -614,7 +689,7 @@ public void CheckGeneration() if (generation != oldGen) { - Util.LogInfo("CharInfo: " + name + " Generation detected: " + generation.ToString()); + Util.LogDetail("CharInfo: " + name + " Generation detected: " + generation.ToString()); Write(); } } @@ -627,7 +702,7 @@ public void CheckGenerationQuick() CheckOverride(); if (generation != oldGen) { - Util.LogInfo("CharInfo: " + name + " Generation detected: " + generation.ToString()); + Util.LogDetail("CharInfo: " + name + " Generation detected: " + generation.ToString()); Write(); } } @@ -657,14 +732,16 @@ public bool HasWrinkleMaps() public bool AnyJsonMaterialPathExists(string path) { - QuickJSON meshJson = MeshJsonData; + QuickJSON objectsJson = ObjectsJsonData; - foreach (MultiValue mvMesh in meshJson.values) + foreach (MultiValue mvMesh in objectsJson.values) { if (mvMesh.Type == MultiType.Object) { QuickJSON objJson = mvMesh.ObjectValue; - QuickJSON materialsJson = objJson.GetObjectAtPath("Materials"); + string objName = mvMesh.Key; + string materialsPath = ObjectsMaterialsJsonPath(objName); + QuickJSON materialsJson = objectsJson.GetObjectAtPath(materialsPath); if (materialsJson != null) { foreach (MultiValue mvMat in materialsJson.values) @@ -688,7 +765,7 @@ public void Release() { jsonData = null; fbx = null; - Util.LogInfo("CharInfo: " + name + " Data Released!"); + Util.LogDetail("CharInfo: " + name + " Data Released!"); } } @@ -711,7 +788,6 @@ public bool CanHaveHighQualityMaterials } } - public void Read() { TextAsset infoAsset = AssetDatabase.LoadAssetAtPath(infoFilepath); @@ -819,6 +895,105 @@ public void Write() writer.Close(); AssetDatabase.ImportAsset(infoFilepath); } - } + public void CheckRadioGroupFlags() + { + if (ImporterWindow.Current == null) + { + Util.LogWarn("The Importer Window is not open - please open the CC/iC importer window before continuing."); + return; + } + + if (ShaderFlags.HasFlag(ShaderFeatureFlags.ClothPhysics)) + { + if (!ImporterWindow.Current.MagicaCloth2Available) + { + ShaderFlags |= ShaderFeatureFlags.UnityClothPhysics; + } + + if (!GroupHasFlagSet(clothGroup)) + { + ShaderFlags |= ShaderFeatureFlags.UnityClothPhysics; + } + } + else + { + if (GroupHasFlagSet(clothGroup)) + { + ShaderFlags |= ShaderFeatureFlags.ClothPhysics; + } + } + + if (ShaderFlags.HasFlag(ShaderFeatureFlags.HairPhysics)) + { + if (!ImporterWindow.Current.MagicaCloth2Available && !ImporterWindow.Current.DynamicBoneAvailable) + { + ShaderFlags |= ShaderFeatureFlags.UnityClothHairPhysics; + } + + if (!GroupHasFlagSet(hairGroup)) + { + ShaderFlags |= ShaderFeatureFlags.UnityClothHairPhysics; + } + } + else + { + if (GroupHasFlagSet(hairGroup)) + { + ShaderFlags |= ShaderFeatureFlags.HairPhysics; + } + } + } + + public void EnsureDefaultsAreSet(ShaderFeatureFlags flag) + { + if (ImporterWindow.Current == null) + { + Util.LogWarn("The Importer Window is not open - please open the CC/iC importer window before continuing."); + return; + } + + // if no alternatives are available or the flags are unset - then set unity physics as a default when activating cloth or hair physics + switch (flag) + { + case ShaderFeatureFlags.ClothPhysics: + { + if (!ImporterWindow.Current.MagicaCloth2Available) + { + ShaderFlags |= ShaderFeatureFlags.UnityClothPhysics; + } + + if (!GroupHasFlagSet(clothGroup)) + { + ShaderFlags |= ShaderFeatureFlags.UnityClothPhysics; + } + + break; + } + case ShaderFeatureFlags.HairPhysics: + { + if (!ImporterWindow.Current.MagicaCloth2Available && !ImporterWindow.Current.DynamicBoneAvailable) + { + ShaderFlags |= ShaderFeatureFlags.UnityClothHairPhysics; + } + + if (!GroupHasFlagSet(hairGroup)) + { + ShaderFlags |= ShaderFeatureFlags.UnityClothHairPhysics; + } + + break; + } + } + } + + public bool GroupHasFlagSet(ShaderFeatureFlags[] group) + { + foreach (ShaderFeatureFlags groupFlag in group) + { + if (ShaderFlags.HasFlag(groupFlag)) return true; + } + return false; + } + } } diff --git a/Editor/ColliderManagerEditor.cs b/Editor/ColliderManagerEditor.cs index 0b0c1de..99198f0 100644 --- a/Editor/ColliderManagerEditor.cs +++ b/Editor/ColliderManagerEditor.cs @@ -18,392 +18,1138 @@ using UnityEngine; using UnityEditor; +using UnityEditor.EditorTools; using ColliderSettings = Reallusion.Import.ColliderManager.ColliderSettings; +using System.Linq; +using System.Collections.Generic; +using System; +using Object = UnityEngine.Object; +using System.Collections; +using UnityEditor.ShortcutManagement; namespace Reallusion.Import { - [CustomEditor(typeof(ColliderManager))] - public class ColliderManagerEditor : Editor - { - private ColliderManager colliderManager; - private ColliderSettings currentCollider; - private bool symmetrical = true; - - const float LABEL_WIDTH = 80f; - const float GUTTER = 40f; - const float BUTTON_WIDTH = 160f; - - public static string CURRENT_COLLIDER_NAME - { - get - { - if (EditorPrefs.HasKey("RL_Current_Collider_Name")) - return EditorPrefs.GetString("RL_Current_Collider_Name"); - return ""; - } + [CustomEditor(typeof(ColliderManager))] + public class ColliderManagerEditor : Editor + { + private bool drawAllGizmos = true; + private bool resetAfterGUI = false; + private bool recallAfterGUI = false; + private Styles colliderManagerStyles; + + private ColliderManager colliderManager; + private ColliderSettings currentCollider; + private Texture2D editModeEnable, editModeDisable, magicaIcon; + private Color baseBackground; + + const float LABEL_WIDTH = 80f; + const float GUTTER = 40f; + const float BUTTON_WIDTH = 160f; + + [SerializeField] private ColliderManager.GizmoState cachedGizmoState; + [SerializeField] private bool editMode = false; + [SerializeField] private bool activeEdit = false; + public static bool EditMode => Current != null && Current.editMode; + public static ColliderManagerEditor Current { get; private set; } + + + public static string CURRENT_COLLIDER_NAME + { + get + { + if (EditorPrefs.HasKey("RL_Current_Collider_Name")) + return EditorPrefs.GetString("RL_Current_Collider_Name"); + return ""; + } - set - { - EditorPrefs.SetString("RL_Current_Collider_Name", value); - } - } + set + { + EditorPrefs.SetString("RL_Current_Collider_Name", value); + } + } - private void OnEnable() - { - colliderManager = (ColliderManager)target; - InitCurrentCollider(CURRENT_COLLIDER_NAME); - } + private void OnEnable() + { + Current = this; + colliderManager = (ColliderManager)target; + InitIcons(); + } + + private void OnDestroy() + { + if (Current == this) Current = null; + } + + private void OnDisable() + { + // + } - private void InitCurrentCollider(string name = null) + private void InitCurrentCollider(string name = null) { currentCollider = null; - if (colliderManager.settings.Length > 0) + if (colliderManager.settings.Length > 0) { if (!string.IsNullOrEmpty(name)) - { - foreach (ColliderSettings cs in colliderManager.settings) - { - if (cs.name == name) - { - currentCollider = cs; - return; - } - } - } + { + foreach (ColliderSettings cs in colliderManager.settings) + { + if (cs.name == name) + { + currentCollider = cs; + return; + } + } + } + + currentCollider = colliderManager.settings[0]; + } + } - currentCollider = colliderManager.settings[0]; - } - } + private void CreateAbstractColliders() + { + Physics.CreateAbstractColliders(colliderManager, out colliderManager.abstractedCapsuleColliders);//, out colliderManager.genericColliderList); + } - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); + private void InitIcons() + { + editModeEnable = Util.FindTexture(new string[] { "Assets", "Packages" }, "RL_Edit_Enable"); + editModeDisable = Util.FindTexture(new string[] { "Assets", "Packages" }, "RL_Edit_Disable"); + //magicaIcon = Util.FindTexture(new string[] { "Assets", "Packages" }, "icon-collider"); + //if (magicaIcon == null) + //{ + magicaIcon = (Texture2D)EditorGUIUtility.IconContent("CircleCollider2D Icon").image; + //} + colliderManager.currentEditType = ColliderManager.ColliderType.Unknown; + colliderManager.magicaCloth2Available = Physics.MagicaCloth2IsAvailable(); + colliderManager.dynamicBoneAvailable = Physics.DynamicBoneIsAvailable(); + } + + public class Styles + { + public GUIStyle sceneLabelText; + public GUIStyle objectLabelText; + public GUIStyle normalButton; + public GUIStyle currentButton; - OnColliderInspectorGUI(); - } + public Styles() + { + sceneLabelText = new GUIStyle(); + sceneLabelText.normal.textColor = Color.cyan; + sceneLabelText.fontSize = 18; - public void OnColliderInspectorGUI() - { - if (currentCollider == null) return; + objectLabelText = new GUIStyle(); + objectLabelText.normal.textColor = Color.red; + objectLabelText.fontSize = 12; - Color background = GUI.backgroundColor; + normalButton = new GUIStyle(GUI.skin.button); + currentButton = new GUIStyle(GUI.skin.button); + currentButton.normal.background = TextureColor(new Color(0.3f, 0.3f, 0.63f, 0.5f)); + } + } - GUILayout.Space(10f); + private void OnSceneGUI() + { + //CatchKeyEvents(); - GUILayout.Label("Adjust Colliders", EditorStyles.boldLabel); + if (colliderManagerStyles == null) colliderManagerStyles = new Styles(); - GUILayout.Space(10f); + string selectedName = ""; - GUILayout.BeginVertical(EditorStyles.helpBox); + if (colliderManager.abstractedCapsuleColliders != null) + { + foreach (ColliderManager.AbstractCapsuleCollider c in colliderManager.abstractedCapsuleColliders) + { + // Color drawCol = c == colliderManager.selectedAbstractCapsuleCollider ? Color.red : new Color(0.60f, 0.9f, 0.60f); + Color drawCol = c == colliderManager.selectedAbstractCapsuleCollider ? Color.red : Color.cyan; + if (colliderManager.selectedAbstractCapsuleCollider == c) + { + selectedName = c.name; + //small floating annotation near the collider + Handles.Label(c.transform.position + Vector3.up * 0.1f + Vector3.left * 0.1f, c.name, colliderManagerStyles.objectLabelText); + if (c.isEnabled) + DrawWireCapsule(c.transform.position, c.transform.rotation, c.radius, c.height, c.axis, drawCol); + switch (colliderManager.manipulator) + { + case ColliderManager.ManipulatorType.position: + { + Vector3 targetPosition = c.transform.position; + EditorGUI.BeginChangeCheck(); + targetPosition = Handles.PositionHandle(targetPosition, c.transform.rotation); + if (EditorGUI.EndChangeCheck()) + { + if (!ColliderManager.AbstractCapsuleCollider.IsNullOrEmpty(colliderManager.mirrorImageAbstractCapsuleCollider)) + { + Vector3 delta = c.transform.parent.InverseTransformPoint(targetPosition) - c.transform.parent.InverseTransformPoint(c.transform.position); + Quaternion inv = Quaternion.Inverse(c.transform.localRotation); + Vector3 diff = inv * delta; + colliderManager.UpdateColliderFromAbstract(c.transform.localPosition, c.transform.localRotation); + } + c.transform.position = targetPosition; + } + break; + } + case ColliderManager.ManipulatorType.rotation: + { + Quaternion targetRotation = c.transform.rotation; + Quaternion currentLocalRotation = c.transform.localRotation; + EditorGUI.BeginChangeCheck(); + targetRotation = Handles.RotationHandle(targetRotation, c.transform.position); + if (EditorGUI.EndChangeCheck()) + { + Quaternion targetLocalRotation = Quaternion.Inverse(c.transform.parent.rotation) * targetRotation; + if (!ColliderManager.AbstractCapsuleCollider.IsNullOrEmpty(colliderManager.mirrorImageAbstractCapsuleCollider)) + { + Vector3 rDiff = targetLocalRotation.eulerAngles - currentLocalRotation.eulerAngles; + colliderManager.UpdateColliderFromAbstract(c.transform.localPosition, targetLocalRotation); + } + c.transform.rotation = targetRotation; + } + + break; + } + case ColliderManager.ManipulatorType.scale: + { + Handles.color = Color.green; + EditorGUI.BeginChangeCheck(); + float h = c.height; + float r = c.radius; + + Vector3 hDir, rDir, r2Dir; + if (c.axis == ColliderManager.ColliderAxis.y) + { + hDir = c.transform.up; + rDir = c.transform.forward; + r2Dir = c.transform.right; + } + else if (c.axis == ColliderManager.ColliderAxis.z) + { + hDir = c.transform.forward; + rDir = c.transform.up; + r2Dir = c.transform.right; + } + else // x + { + hDir = c.transform.right; + rDir = c.transform.forward; + r2Dir = c.transform.up; + } + hDir *= h * 0.5f; + rDir *= r; + r2Dir *= r; + Vector3 hDelta = SceneView.lastActiveSceneView.camera.WorldToViewportPoint(c.transform.position + hDir) - + SceneView.lastActiveSceneView.camera.WorldToViewportPoint(c.transform.position); + Vector3 rDelta = SceneView.lastActiveSceneView.camera.WorldToViewportPoint(c.transform.position + rDir) - + SceneView.lastActiveSceneView.camera.WorldToViewportPoint(c.transform.position); + Vector3 r2Delta = SceneView.lastActiveSceneView.camera.WorldToViewportPoint(c.transform.position + r2Dir) - + SceneView.lastActiveSceneView.camera.WorldToViewportPoint(c.transform.position); + if (Mathf.Abs(r2Delta.x) > Mathf.Abs(rDelta.x)) + { + rDelta = r2Delta; + rDir = r2Dir; + } + + float hSign = 1f; + float rSign = 1f; + if (Mathf.Abs(hDelta.x) > Mathf.Abs(hDelta.y)) hSign = -Mathf.Sign(hDelta.x); + else hSign = -Mathf.Sign(hDelta.y); + if (Mathf.Abs(rDelta.x) > Mathf.Abs(rDelta.y)) rSign = Mathf.Sign(rDelta.x); + else rSign = Mathf.Sign(rDelta.y); + Vector3 hPos = c.transform.position - (hDir * hSign); + h = Handles.ScaleValueHandle(h, hPos, + c.transform.rotation * Quaternion.Euler(90, 0, 0), + HandleUtility.GetHandleSize(hPos), + Handles.DotHandleCap, 1); + + Handles.DrawWireArc(c.transform.position, + c.transform.up, + -c.transform.right, + 180, + r); + + Vector3 rPos = c.transform.position + (rDir * rSign); + r = Handles.ScaleValueHandle(r, rPos, + c.transform.rotation, + HandleUtility.GetHandleSize(rPos), + Handles.DotHandleCap, 1); + + if (EditorGUI.EndChangeCheck()) + { + c.radius = r; + c.height = h; + colliderManager.UpdateColliderFromAbstract(c.transform.localPosition, c.transform.localRotation); + } + break; + } + } + } + else + { + if (Selection.objects.Contains(colliderManager.gameObject)) + { + drawAllGizmos = false; + if (colliderManager.mirrorImageAbstractCapsuleCollider == c) + DrawWireCapsule(c.transform.position, c.transform.rotation, c.radius, c.height, c.axis, Color.magenta); + } + else + drawAllGizmos = true; + + if (drawAllGizmos) + { + if (c.isEnabled) + { + if (colliderManager.mirrorImageAbstractCapsuleCollider == c) drawCol = Color.magenta; + DrawWireCapsule(c.transform.position, c.transform.rotation, c.radius, c.height, c.axis, drawCol); + } + } + } + } + // always writes screen text when in edit mode or a collider is selected for editing + if (activeEdit || editMode) + { + //large fixed text on the scene view + Handles.BeginGUI(); + string lockString = ActiveEditorTracker.sharedTracker.isLocked ? "Locked" : "Unlocked"; + string modeString = colliderManager.manipulatorArray[(int)colliderManager.manipulator]; + string displayString = "Inspector Status: " + lockString + "\nSelected Collider: " + selectedName + "\nMode: " + modeString; + + GUI.Label(new Rect(55, 7, 1000, 1000), displayString, colliderManagerStyles.sceneLabelText); + Handles.EndGUI(); + } + } + } - // custom collider adjuster - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("Collider", GUILayout.Width(LABEL_WIDTH)); - if (EditorGUILayout.DropdownButton( - new GUIContent(currentCollider.name), - FocusType.Passive - )) - { - GenericMenu menu = new GenericMenu(); - foreach (ColliderSettings c in colliderManager.settings) - { - menu.AddItem(new GUIContent(c.name), c == currentCollider, SelectCurrentCollider, c); - } - menu.ShowAsContext(); - } - GUILayout.EndHorizontal(); + public void CatchKeyEvents() + { + Event e = Event.current; + if (e.type == EventType.KeyUp) + { + + } - GUILayout.Space(8f); + if (e.type == EventType.KeyDown) + { + + } + } - EditorGUI.BeginChangeCheck(); + public void SyncMode() + { + switch (Tools.current) + { + case Tool.Move: + { + if (colliderManager.manipulator != ColliderManager.ManipulatorType.position) + { + colliderManager.manipulator = ColliderManager.ManipulatorType.position; + } + break; + } + case Tool.Rotate: + { + if (colliderManager.manipulator != ColliderManager.ManipulatorType.rotation) + { + colliderManager.manipulator = ColliderManager.ManipulatorType.rotation; + } + break; + } + case Tool.Scale: + { + if(colliderManager.manipulator != ColliderManager.ManipulatorType.scale) + { + colliderManager.manipulator = ColliderManager.ManipulatorType.scale; + } + break; + } + } + } - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("Radius", GUILayout.Width(LABEL_WIDTH)); - currentCollider.radiusAdjust = EditorGUILayout.Slider(currentCollider.radiusAdjust, -0.1f, 0.1f); - GUILayout.EndHorizontal(); - if (currentCollider.collider.GetType() == typeof(CapsuleCollider)) - { - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("Height", GUILayout.Width(LABEL_WIDTH)); - currentCollider.heightAdjust = EditorGUILayout.Slider(currentCollider.heightAdjust, -0.1f, 0.1f); - GUILayout.EndHorizontal(); - } + public override void OnInspectorGUI() + { + //CatchKeyEvents(); + SyncMode(); - GUILayout.Space(8f); - - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("X (loc)", GUILayout.Width(LABEL_WIDTH)); - currentCollider.xAdjust = EditorGUILayout.Slider(currentCollider.xAdjust, -0.1f, 0.1f); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("Y (loc)", GUILayout.Width(LABEL_WIDTH)); - currentCollider.yAdjust = EditorGUILayout.Slider(currentCollider.yAdjust, -0.1f, 0.1f); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("Z (loc)", GUILayout.Width(LABEL_WIDTH)); - currentCollider.zAdjust = EditorGUILayout.Slider(currentCollider.zAdjust, -0.1f, 0.1f); - GUILayout.EndHorizontal(); - - GUILayout.Space(8f); - - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("X (rot)", GUILayout.Width(LABEL_WIDTH)); - currentCollider.xRotate = EditorGUILayout.Slider(currentCollider.xRotate, -90f, 90f); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("Y (rot)", GUILayout.Width(LABEL_WIDTH)); - currentCollider.yRotate = EditorGUILayout.Slider(currentCollider.yRotate, -90f, 90f); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("Z (rot)", GUILayout.Width(LABEL_WIDTH)); - currentCollider.zRotate = EditorGUILayout.Slider(currentCollider.zRotate, -90f, 90f); - GUILayout.EndHorizontal(); - - GUILayout.Space(8f); - - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("Symetrical", GUILayout.Width(LABEL_WIDTH)); - symmetrical = EditorGUILayout.Toggle(symmetrical); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("", GUILayout.Width(LABEL_WIDTH)); - if (GUILayout.Button("Reset", GUILayout.Width(80f))) - { - currentCollider.Reset(); - if (symmetrical) UpdateSymmetrical(SymmetricalUpdateType.Reset); - } - GUILayout.Space(10f); - if (GUILayout.Button("Set", GUILayout.Width(80f))) - { - currentCollider.FetchSettings(); - if (symmetrical) UpdateSymmetrical(SymmetricalUpdateType.Fetch); - } - GUILayout.EndHorizontal(); - GUILayout.Space(10f); - GUILayout.BeginHorizontal(); - GUILayout.Space(GUTTER); - GUILayout.Label("", GUILayout.Width(LABEL_WIDTH)); - if (GUILayout.Button("Select", GUILayout.Width(80f))) + if (colliderManager.abstractedCapsuleColliders == null || + colliderManager.abstractedCapsuleColliders.Count == 0) { - Selection.activeObject = currentCollider.collider; + CreateAbstractColliders(); } - GUILayout.EndHorizontal(); + if (editModeEnable == null) InitIcons(); + if (colliderManagerStyles == null) colliderManagerStyles = new Styles(); - if (EditorGUI.EndChangeCheck()) - { - currentCollider.Update(); - if (symmetrical) UpdateSymmetrical(SymmetricalUpdateType.Update); - } + baseBackground = GUI.backgroundColor; + base.OnInspectorGUI(); - GUILayout.EndVertical(); + DrawEditAssistBlock(); + //DrawColliderSetSelector(); + if (editMode) + { + DrawColliderSelectionBlock(); + DrawStoreControls(); + } - GUILayout.Space(10f); + if (colliderManager.clothMeshes != null && colliderManager.clothMeshes.Length > 0) + { + DrawClothShortcuts(); + } - EditorGUILayout.HelpBox("If changing the colliders directly, use the Rebuild Settings button to update to the new Collider settings.", MessageType.Info, true); + if (resetAfterGUI) + { + // optional: deselect the collider for editing + bool deSelectChar = false; + if (deSelectChar) + { + DeSelectColliderForEdit(); + } + + // reset the collider to the cached values + colliderManager.ResetColliderFromCache(); + + SceneView.RepaintAll(); + resetAfterGUI = false; + } - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("Rebuild Settings", GUILayout.Width(BUTTON_WIDTH))) - { - colliderManager.RefreshData(); - InitCurrentCollider(CURRENT_COLLIDER_NAME); - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); + if (recallAfterGUI) + { + PhysicsSettingsStore.RecallAbstractColliderSettings(colliderManager, false); + recallAfterGUI = false; + } + } - GUILayout.Space(10f); + private void DrawEditAssistBlock() + { + GUILayout.Space(10f); + GUILayout.Label("Collider Edit Mode", EditorStyles.boldLabel); + GUILayout.Space(10f); + GUI.backgroundColor = editMode ? Color.Lerp(baseBackground, Color.green, 0.9f) : baseBackground; + GUILayout.BeginVertical(EditorStyles.helpBox); + GUI.backgroundColor = baseBackground; + + GUILayout.BeginVertical(); + GUILayout.Space(10f); + + GUILayout.BeginHorizontal(); + GUILayout.Space(10f); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + EditorGUI.BeginChangeCheck(); + + // Icons from Wrench icon by Icons8 + + editMode = ActiveEditorTracker.sharedTracker.isLocked; + //string lookIcon = locked ? "d_SceneViewVisibility" : "ViewToolOrbit"; + //Texture2D lookIconImage = (Texture2D)EditorGUIUtility.IconContent(lookIcon).image; + Texture2D lookIconImage = editMode ? editModeDisable : editModeEnable; + if (GUILayout.Button(new GUIContent(lookIconImage, (editMode ? "EXIT from" : "ENTER") + " Collider Edit Mode.\n" + (editMode ? "This will UNLOCK the inspctor and reselect the character - drawing all the default gizmos" : "This will LOCK the inspector and deselect the character - showing only the gizmos of editable colliders and preventing loss of focus on the character.")), GUILayout.Width(48f), GUILayout.Height(48f))) + { + if (!editMode) + { + SetEditAssistMode(); + } + else + { + UnSetEditAssistMode(); + } + } + if (EditorGUI.EndChangeCheck()) + { + SceneView.RepaintAll(); + } + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + GUIStyle wrap = new GUIStyle(GUI.skin.button); + wrap.wordWrap = true; + string editModeInactiveText = "Collider Edit Mode allows convenient editing of the phycics colliders. This will LOCK the inspector to the character and an only draw the gizmos for the editable colliders."; //edit removed: This will provide a less cluttered view and avoid loss of character focus causing issues. + string editModeActiveText = "Collider Edit Mode is currently ACTIVE: The inspector is currently locked to the character. Click the button to deactivate."; + EditorGUILayout.HelpBox(editMode ? editModeActiveText : editModeInactiveText, MessageType.Info, true); + + GUILayout.Space(10f); + GUILayout.EndHorizontal(); + GUILayout.EndVertical(); + + GUILayout.Space(10f); + GUILayout.EndVertical(); //(EditorStyles.helpBox); + } + + public void SetEditAssistMode() + { + CreateAbstractColliders(); + Tools.hidden = true; + editMode = true; + if (colliderManager != null) + { + if (colliderManager.selectedAbstractCapsuleCollider != null) + { + if (colliderManager.selectedAbstractCapsuleCollider.transform != null) + FocusPosition(colliderManager.selectedAbstractCapsuleCollider.transform.position); + } + } + Selection.activeObject = colliderManager.gameObject; + ActiveEditorTracker.sharedTracker.isLocked = editMode; + //ActiveEditorTracker.sharedTracker.ForceRebuild(); + SetGizmos(); + Selection.activeObject = null; + SceneView.RepaintAll(); + if (EditorApplication.isPlaying) + { + WindowManager.GrabLastSceneFocus(); + } + } - EditorGUILayout.HelpBox("Settings can be saved in play mode and reloaded after play mode ends.", MessageType.Info, true); + public void UnSetEditAssistMode() + { + Tools.hidden = false; + // optional: deselect the collider for editing + bool deSelectChar = false; + if (deSelectChar) + { + DeSelectColliderForEdit(); + } - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - GUI.backgroundColor = Color.Lerp(background, Color.red, 0.25f); - if (GUILayout.Button("Save Settings", GUILayout.Width(BUTTON_WIDTH))) - { - PhysicsSettingsStore.SaveColliderSettings(colliderManager); - } - GUI.backgroundColor = background; - GUILayout.Space(10f); - GUI.backgroundColor = Color.Lerp(background, Color.yellow, 0.25f); - if (GUILayout.Button("Recall Settings", GUILayout.Width(BUTTON_WIDTH))) - { - PhysicsSettingsStore.RecallColliderSettings(colliderManager); - } - GUI.backgroundColor = background; - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); + editMode = false; + ActiveEditorTracker.sharedTracker.isLocked = false; + //ActiveEditorTracker.sharedTracker.ForceRebuild(); + ResetGizmos(); + Selection.activeObject = colliderManager.gameObject; + SceneView.RepaintAll(); + } - GUILayout.Space(10f); + private void DrawColliderSetSelector() + { + GUILayout.Space(10f); + GUILayout.Label("Collider Type Select", EditorStyles.boldLabel); + GUILayout.Space(10f); + GUI.backgroundColor = editMode ? Color.Lerp(baseBackground, Color.green, 0.9f) : baseBackground; + GUILayout.BeginVertical(EditorStyles.helpBox); + GUI.backgroundColor = baseBackground; + + GUILayout.BeginHorizontal(); // controls + text + GUILayout.Space(10f); + + GUILayout.BeginVertical(); // controls group for vertical centering + GUILayout.Space(10f); // enforce a minimum upper border + GUILayout.FlexibleSpace(); + + GUILayout.BeginHorizontal(); // controls group horizontal layout + float iconSize = 36f; + + bool active = colliderManager.currentEditType.HasFlag(ColliderManager.ColliderType.UnityEngine); + GUI.backgroundColor = active ? Color.Lerp(baseBackground, Color.blue, 0.35f) : baseBackground; + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("CapsuleCollider Icon").image, "Native UnityEngine colliders"), GUILayout.Width(iconSize), GUILayout.Height(iconSize))) + { + if (active) + colliderManager.currentEditType ^= ColliderManager.ColliderType.UnityEngine; + else + colliderManager.currentEditType |= ColliderManager.ColliderType.UnityEngine; + } + GUI.backgroundColor = baseBackground; - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (Application.isPlaying) GUI.enabled = false; - GUI.backgroundColor = Color.Lerp(background, Color.cyan, 0.25f); - if (GUILayout.Button("Apply to Prefab", GUILayout.Width(BUTTON_WIDTH))) - { - UpdatePrefab(colliderManager); - } - GUI.enabled = true; - GUI.backgroundColor = background; - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - GUILayout.Space(10f); - - GUILayout.BeginHorizontal(); - GUILayout.Label("Cloth Meshes", EditorStyles.boldLabel); - GUILayout.BeginVertical(); - - GUI.backgroundColor = Color.Lerp(background, Color.green, 0.25f); - foreach (GameObject clothMesh in colliderManager.clothMeshes) - { - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button(clothMesh.name, GUILayout.Width(160f))) - { - Selection.activeObject = clothMesh; - } - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - GUILayout.Space(4f); - } - GUI.backgroundColor = background; - GUILayout.EndVertical(); - GUILayout.EndHorizontal(); - } + GUILayout.Space(4f); - public void UpdatePrefab(Object component) - { - WindowManager.HideAnimationPlayer(true); - WindowManager.HideAnimationRetargeter(true); - - GameObject prefabRoot = PrefabUtility.GetOutermostPrefabInstanceRoot(component); - if (prefabRoot) - { - // reset collider states - ColliderManager colliderManager = prefabRoot.GetComponentInChildren(); - if (colliderManager) - { - foreach (ColliderSettings cs in colliderManager.settings) - { - cs.Reset(true); - } - } + active = colliderManager.currentEditType.HasFlag(ColliderManager.ColliderType.MagicaCloth2); + GUI.backgroundColor = active ? Color.Lerp(baseBackground, Color.blue, 0.35f) : baseBackground; + if (GUILayout.Button(new GUIContent(magicaIcon, "Magica Cloth 2 colliders"), GUILayout.Width(iconSize), GUILayout.Height(iconSize))) + { + if (active) + colliderManager.currentEditType ^= ColliderManager.ColliderType.MagicaCloth2; + else + colliderManager.currentEditType |= ColliderManager.ColliderType.MagicaCloth2; + } + GUI.backgroundColor = baseBackground; - // save prefab asset - PrefabUtility.ApplyPrefabInstance(prefabRoot, InteractionMode.UserAction); - } - } + GUILayout.Space(4f); - enum SymmetricalUpdateType { None, Update, Fetch, Reset } + active = colliderManager.currentEditType.HasFlag(ColliderManager.ColliderType.DynamicBone); + GUI.backgroundColor = active ? Color.Lerp(baseBackground, Color.blue, 0.35f) : baseBackground; + if(GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("FixedJoint Icon").image, "Dynamic Bone colliders"), GUILayout.Width(iconSize), GUILayout.Height(iconSize))) + { + if (active) + colliderManager.currentEditType ^= ColliderManager.ColliderType.DynamicBone; + else + colliderManager.currentEditType |= ColliderManager.ColliderType.DynamicBone; + } + GUI.backgroundColor = baseBackground; - private void UpdateSymmetrical(SymmetricalUpdateType type) - { - string name = currentCollider.name; + GUILayout.Space(4f); + GUILayout.EndHorizontal(); // controls group horizontal layout - string boneName = name.Remove(name.IndexOf("_Capsule")); - string symName = null; - //Debug.Log(boneName); + GUILayout.FlexibleSpace(); + GUILayout.Space(10f); // enforce a minimum lower border + GUILayout.EndVertical(); // controls group for vertical centering - if (boneName.Contains("_L_")) - { - symName = boneName.Replace("_L_", "_R_"); - } - else if (boneName.Contains("_R_")) - { - symName = boneName.Replace("_R_", "_L_"); - } - else if (boneName.Contains("_Hip")) - { - symName = boneName; - } - if (!string.IsNullOrEmpty(symName)) - { - foreach (ColliderSettings cs in colliderManager.settings) + GUILayout.BeginVertical(); // text for vertical centering + GUILayout.FlexibleSpace(); + + string labelText = WriteLabelText(colliderManager.currentEditType); + + GUILayout.Label(labelText); + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); // text for vertical centering + GUILayout.FlexibleSpace(); // left justify the text area + GUILayout.EndHorizontal(); // controls + text + + GUILayout.EndVertical(); // (EditorStyles.helpBox); + } + + private string WriteLabelText(ColliderManager.ColliderType source) + { + string labelText = ""; + bool newlineNeeded = false; + + if (source.HasFlag(ColliderManager.ColliderType.UnityEngine)) + { + labelText += "Native UnityEngine"; + newlineNeeded = true; + } + + if (source.HasFlag(ColliderManager.ColliderType.MagicaCloth2)) + { + if (newlineNeeded) labelText += "\n"; + labelText += "Magica Cloth 2"; + newlineNeeded = true; + } + + if (source.HasFlag(ColliderManager.ColliderType.DynamicBone)) + { + if (newlineNeeded) labelText += "\n"; + labelText += "DynamicBone"; + newlineNeeded = true; + } + + return labelText; + } + + private void DrawColliderSelectionBlock() + { + GUILayout.Space(10f); + GUILayout.Label("Adjust Colliders", EditorStyles.boldLabel); + GUILayout.Space(10f); + GUI.backgroundColor = editMode ? Color.Lerp(baseBackground, Color.green, 0.9f) : baseBackground; + GUILayout.BeginVertical(EditorStyles.helpBox); + GUI.backgroundColor = baseBackground; + + GUILayout.BeginVertical(); + GUILayout.Space(10f); + if (colliderManager.abstractedCapsuleColliders != null) + { + foreach (ColliderManager.AbstractCapsuleCollider c in colliderManager.abstractedCapsuleColliders) + { + bool active = (c == colliderManager.selectedAbstractCapsuleCollider); + GUILayout.BeginVertical(); + GUILayout.Space(active ? 1f : 0f); + GUILayout.BeginHorizontal(); + + GUILayout.FlexibleSpace(); + + EditorGUI.BeginDisabledGroup(!c.isEnabled); + GUI.backgroundColor = active ? Color.Lerp(baseBackground, Color.blue, 0.35f) : baseBackground; + if (GUILayout.Button(c.name, GUILayout.MaxWidth(250f))) + //if (GUILayout.Button(c.name, (active ? colliderManagerStyles.currentButton : colliderManagerStyles.normalButton), GUILayout.MaxWidth(250f))) + { + SelectColliderForEdit(c); + } + GUI.backgroundColor = baseBackground; + EditorGUI.EndDisabledGroup(); + // off button + GUILayout.Space(4f); + EditorGUI.BeginChangeCheck(); + c.isEnabled = GUILayout.Toggle(c.isEnabled, ""); + if (EditorGUI.EndChangeCheck()) + { + if (c.isEnabled) + { + c.transform.gameObject.SetActive(true); + if (colliderManager.transformSymmetrically) + { + ColliderManager.AbstractCapsuleCollider m = DetermineMirrorImageCollider(c); + if (m != null) + { + m.isEnabled = true; + m.transform.gameObject.SetActive(true); + } + } + } + else + { + c.transform.gameObject.SetActive(false); + if (colliderManager.transformSymmetrically) + { + ColliderManager.AbstractCapsuleCollider m = DetermineMirrorImageCollider(c); + if (m != null) + { + m.isEnabled = false; + m.transform.gameObject.SetActive(false); + } + } + DeSelectColliderForEdit(); + } + SceneView.RepaintAll(); + } + // end of off button + + GUILayout.FlexibleSpace(); + + GUILayout.EndHorizontal(); + + if (active) + { + GUILayout.Space(0f); + DrawEditModeControls(); + } + + GUILayout.EndVertical(); + if (active) + GUILayout.Space(1f); + + + } + } + GUILayout.Space(10f); + + GUILayout.BeginHorizontal(); + GUILayout.Space(10f); + + EditorGUI.BeginChangeCheck(); + colliderManager.transformSymmetrically = GUILayout.Toggle(colliderManager.transformSymmetrically, new GUIContent("Symmetrical Transformation")); + if (EditorGUI.EndChangeCheck()) + { + SceneView.RepaintAll(); + if (!ColliderManager.AbstractCapsuleCollider.IsNullOrEmpty(colliderManager.selectedAbstractCapsuleCollider)) + { + colliderManager.mirrorImageAbstractCapsuleCollider = DetermineMirrorImageCollider(colliderManager.selectedAbstractCapsuleCollider); + FocusPosition(colliderManager.selectedAbstractCapsuleCollider.transform.position); + } + } + GUILayout.Space(10f); + EditorGUI.BeginChangeCheck(); + colliderManager.frameSymmetryPair = GUILayout.Toggle(colliderManager.frameSymmetryPair, new GUIContent("Frame Pair")); + if (EditorGUI.EndChangeCheck()) + { + SceneView.RepaintAll(); + if (!ColliderManager.AbstractCapsuleCollider.IsNullOrEmpty(colliderManager.selectedAbstractCapsuleCollider)) + { + colliderManager.mirrorImageAbstractCapsuleCollider = DetermineMirrorImageCollider(colliderManager.selectedAbstractCapsuleCollider); + FocusPosition(colliderManager.selectedAbstractCapsuleCollider.transform.position); + } + } + + GUILayout.EndHorizontal(); + + GUILayout.Space(10f); + GUILayout.EndVertical(); + + GUILayout.EndVertical(); + } + + private void SelectColliderForEdit(ColliderManager.AbstractCapsuleCollider c) + { + //SetGizmos(); + activeEdit = true; + if (!SceneView.lastActiveSceneView.drawGizmos && !editMode) + SceneView.lastActiveSceneView.drawGizmos = true; + colliderManager.selectedAbstractCapsuleCollider = c; + colliderManager.mirrorImageAbstractCapsuleCollider = DetermineMirrorImageCollider(c); + colliderManager.CacheCollider(colliderManager.selectedAbstractCapsuleCollider, colliderManager.mirrorImageAbstractCapsuleCollider); + + if (AnimPlayerGUI.IsPlayerShown()) + { + AnimPlayerGUI.ForbidTracking(); + } + + FocusPosition(colliderManager.selectedAbstractCapsuleCollider.transform.position); + + SceneView.RepaintAll(); + } + + private void DeSelectColliderForEdit() + { + if (AnimPlayerGUI.IsPlayerShown()) + { + AnimPlayerGUI.AllowTracking(); + } + + colliderManager.selectedAbstractCapsuleCollider = null; + colliderManager.mirrorImageAbstractCapsuleCollider = null; + activeEdit = false; + + SceneView.RepaintAll(); + } + + public void DrawEditModeControls() + { + // centralize controls + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + // button strip to match selection button width + GUILayout.BeginHorizontal(GUILayout.MaxWidth(270f)); + GUILayout.Space(10f); + + GUI.backgroundColor = colliderManager.manipulator == ColliderManager.ManipulatorType.position ? Color.Lerp(baseBackground, Color.blue, 0.35f) : baseBackground; + //GUIStyle style = (colliderManager.manipulator == ColliderManager.ManipulatorType.position ? colliderManagerStyles.currentButton : colliderManagerStyles.normalButton); + //if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("d_MoveTool on").image, "Transform position tool"), style, GUILayout.Width(30f))) + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("d_MoveTool on").image, "Transform position tool"), GUILayout.Width(30f))) + { + colliderManager.manipulator = ColliderManager.ManipulatorType.position; + Tools.current = Tool.Move; + //SceneView.RepaintAll(); + } + GUI.backgroundColor = baseBackground; + + GUI.backgroundColor = colliderManager.manipulator == ColliderManager.ManipulatorType.rotation ? Color.Lerp(baseBackground, Color.blue, 0.35f) : baseBackground; + //style = (colliderManager.manipulator == ColliderManager.ManipulatorType.rotation ? colliderManagerStyles.currentButton : colliderManagerStyles.normalButton); + //if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("d_RotateTool On").image, "Transform rotation tool"), style, GUILayout.Width(30f))) + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("d_RotateTool On").image, "Transform rotation tool"), GUILayout.Width(30f))) + { + colliderManager.manipulator = ColliderManager.ManipulatorType.rotation; + Tools.current = Tool.Rotate; + //SceneView.RepaintAll(); + } + GUI.backgroundColor = baseBackground; + + GUI.backgroundColor = colliderManager.manipulator == ColliderManager.ManipulatorType.scale ? Color.Lerp(baseBackground, Color.blue, 0.35f) : baseBackground; + //style = (colliderManager.manipulator == ColliderManager.ManipulatorType.scale ? colliderManagerStyles.currentButton : colliderManagerStyles.normalButton); + //if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("ScaleTool On").image, "Transform scale tool"), style, GUILayout.Width(30f))) + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("ScaleTool On").image, "Transform scale tool"), GUILayout.Width(30f))) + { + colliderManager.manipulator = ColliderManager.ManipulatorType.scale; + Tools.current = Tool.Scale; + //SceneView.RepaintAll(); + } + GUI.backgroundColor = baseBackground; + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("d_TreeEditor.Trash").image, "Undo Changes"), colliderManagerStyles.normalButton, GUILayout.Width(30f))) + { + resetAfterGUI = true; + } + + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("d_TreeEditor.Refresh").image, "Reset Collider To Default"), colliderManagerStyles.normalButton, GUILayout.Width(30f))) + { + colliderManager.ResetSingleAbstractCollider(PhysicsSettingsStore.RecallAbstractColliderSettings(colliderManager, true), colliderManager.selectedAbstractCapsuleCollider.name, colliderManager.transformSymmetrically); + } + + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("d_clear").image, "Only Deselect Collider"), colliderManagerStyles.normalButton, GUILayout.Width(30f))) + { + DeSelectColliderForEdit(); + } + GUILayout.Space(10f); + GUILayout.EndHorizontal(); + + // off button + GUILayout.Space(24f); + // end of off button + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + + public void DrawStoreControls() + { + GUILayout.Space(10f); + GUILayout.Label("Save and Recall Colliders", EditorStyles.boldLabel); + GUILayout.Space(10f); + + GUI.backgroundColor = editMode ? Color.Lerp(baseBackground, Color.green, 0.9f) : baseBackground; + GUILayout.BeginVertical(EditorStyles.helpBox); + GUI.backgroundColor = baseBackground; + GUILayout.Space(10f); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUI.backgroundColor = Color.Lerp(baseBackground, Color.red, 0.25f); + GUIContent saveLabel = new GUIContent("Save Settings", "Save the current collider settings to disk - this will overwrite any previously saved settings"); + if (GUILayout.Button(saveLabel, GUILayout.Width(120f), GUILayout.MinWidth(100f))) + { + PhysicsSettingsStore.SaveAbstractColliderSettings(colliderManager, colliderManager.abstractedCapsuleColliders); + } + GUI.backgroundColor = baseBackground; + GUILayout.FlexibleSpace(); + GUI.backgroundColor = Color.Lerp(baseBackground, Color.yellow, 0.25f); + GUIContent recallLabel = new GUIContent("Recall Settings", "Recall any previously saved collider settings"); + if (GUILayout.Button(recallLabel, GUILayout.Width(120f), GUILayout.MinWidth(100f))) + { + colliderManager.ResetAbstractColliders(PhysicsSettingsStore.RecallAbstractColliderSettings(colliderManager, false)); + Repaint(); + SceneView.RepaintAll(); + } + GUI.backgroundColor = baseBackground; + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.Space(10f); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (Application.isPlaying) GUI.enabled = false; + GUI.backgroundColor = Color.Lerp(baseBackground, Color.magenta, 0.25f); + GUIContent resetLabel = new GUIContent("Reset All to Defaults", "This will retrieve and apply the default collider layout to the character."); + if (GUILayout.Button(resetLabel, GUILayout.Width(BUTTON_WIDTH))) + { + colliderManager.ResetAbstractColliders(PhysicsSettingsStore.RecallAbstractColliderSettings(colliderManager, true)); + Repaint(); + SceneView.RepaintAll(); + } + GUI.enabled = true; + GUI.backgroundColor = baseBackground; + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.Space(10f); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (Application.isPlaying) GUI.enabled = false; + GUI.backgroundColor = Color.Lerp(baseBackground, Color.cyan, 0.25f); + GUIContent applyLabel = new GUIContent("Apply to Prefab", "Save the current collider settings to the character prefab."); + if (GUILayout.Button(applyLabel, GUILayout.Width(BUTTON_WIDTH))) + { + CommitPrefab(colliderManager); + } + GUI.enabled = true; + GUI.backgroundColor = baseBackground; + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.Space(10f); + + GUILayout.EndVertical();// (EditorStyles.helpBox); + } + + public void CommitPrefab(Object obj) + { + WindowManager.HideAnimationPlayer(true); + WindowManager.HideAnimationRetargeter(true); + + GameObject prefabRoot = PrefabUtility.GetOutermostPrefabInstanceRoot(obj); + if (prefabRoot) + { + // save prefab asset + PrefabUtility.ApplyPrefabInstance(prefabRoot, InteractionMode.UserAction); + } + } + + public void DrawClothShortcuts() + { + GUILayout.Space(10f); + GUILayout.Label("Available Cloth Meshes", EditorStyles.boldLabel); + GUILayout.Space(10f); + + GUI.backgroundColor = editMode ? Color.Lerp(baseBackground, Color.green, 0.9f) : baseBackground; + GUILayout.BeginVertical(EditorStyles.helpBox); + GUI.backgroundColor = baseBackground; + GUILayout.Space(10f); + + GUILayout.BeginVertical(); + + GUI.backgroundColor = Color.Lerp(baseBackground, Color.green, 0.25f); + if (colliderManager.clothMeshes != null) + { + foreach (GameObject clothMesh in colliderManager.clothMeshes) + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button(clothMesh.name, GUILayout.Width(160f))) + { + Selection.activeObject = clothMesh; + } + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + GUILayout.Space(4f); + } + } + GUI.backgroundColor = baseBackground; + GUILayout.EndVertical(); + GUILayout.Space(6f); + GUILayout.EndVertical();// (EditorStyles.helpBox); + } + + // see: https://forum.unity.com/threads/drawing-capsule-gizmo.354634/#post-4100557 + public static void DrawWireCapsule(Vector3 _pos, Quaternion _rot, float _radius, float _height, ColliderManager.ColliderAxis _axis, Color _color = default(Color)) + { + if (_axis == ColliderManager.ColliderAxis.z) + _rot = _rot * Quaternion.AngleAxis(90f, Vector3.right); + if (_color != default(Color)) + Handles.color = _color; + Matrix4x4 angleMatrix = Matrix4x4.TRS(_pos, _rot, Handles.matrix.lossyScale); + using (new Handles.DrawingScope(angleMatrix)) + { + var pointOffset = (_height - (_radius * 2)) / 2; + + //draw sideways + Handles.DrawWireArc(Vector3.up * pointOffset, Vector3.left, Vector3.back, -180, _radius); + Handles.DrawLine(new Vector3(0, pointOffset, -_radius), new Vector3(0, -pointOffset, -_radius)); + Handles.DrawLine(new Vector3(0, pointOffset, _radius), new Vector3(0, -pointOffset, _radius)); + Handles.DrawWireArc(Vector3.down * pointOffset, Vector3.left, Vector3.back, 180, _radius); + //draw frontways + Handles.DrawWireArc(Vector3.up * pointOffset, Vector3.back, Vector3.left, 180, _radius); + Handles.DrawLine(new Vector3(-_radius, pointOffset, 0), new Vector3(-_radius, -pointOffset, 0)); + Handles.DrawLine(new Vector3(_radius, pointOffset, 0), new Vector3(_radius, -pointOffset, 0)); + Handles.DrawWireArc(Vector3.down * pointOffset, Vector3.back, Vector3.left, -180, _radius); + //draw center + Handles.DrawWireDisc(Vector3.up * pointOffset, Vector3.up, _radius); + Handles.DrawWireDisc(Vector3.down * pointOffset, Vector3.up, _radius); + + } + } + + public static Texture2D TextureColor(Color color) + { + const int size = 32; + Texture2D texture = new Texture2D(size, size); + Color[] pixels = texture.GetPixels(); + for (int i = 0; i < pixels.Length; i++) + { + pixels[i] = color; + } + texture.SetPixels(pixels); + texture.Apply(true); + return texture; + } + + public void FocusPosition(Vector3 pos) + { + Bounds framingBounds; + float mult = 0.35f; + + if (colliderManager.transformSymmetrically && colliderManager.frameSymmetryPair && !ColliderManager.AbstractCapsuleCollider.IsNullOrEmpty(colliderManager.mirrorImageAbstractCapsuleCollider)) + { + Vector3 diff = colliderManager.mirrorImageAbstractCapsuleCollider.transform.position + colliderManager.selectedAbstractCapsuleCollider.transform.position; + Vector3 mid = diff / 2; + float mag = diff.magnitude; + + if (mag > 2) + mult = mag * 0.15f; + else + mult = mag * 0.4f; + + framingBounds = new Bounds(mid, Vector3.one * mult); + } + else + framingBounds = new Bounds(pos, Vector3.one * mult); + + SceneView.lastActiveSceneView.Frame(framingBounds, false); + //SceneView.lastActiveSceneView.rotation = Quaternion.Euler(180f, 0f, 180f); + } + + private ColliderManager.AbstractCapsuleCollider DetermineMirrorImageCollider(ColliderManager.AbstractCapsuleCollider collider) + { + if (!colliderManager.transformSymmetrically) { return null; } + + if (colliderManager.DetermineMirrorImageColliderName(collider.name, out string mirrorName, out colliderManager.selectedMirrorPlane)) + return colliderManager.abstractedCapsuleColliders.Find(x => x.name == mirrorName); + else + return null; + } + + public void SetGizmos() + { + // turn on gizmo display (if off) and in 2022.1 or above can supress the drawing + // of certain gizmos and icons for a cleaner scene + cachedGizmoState = new ColliderManager.GizmoState(); + //ColliderManager.GizmoState state = colliderManager.cachedGizmoState; + cachedGizmoState.gizmosEnabled = SceneView.lastActiveSceneView.drawGizmos; + if (!cachedGizmoState.gizmosEnabled) + SceneView.lastActiveSceneView.drawGizmos = true; + +#if UNITY_2022_1_OR_NEWER + colliderManager.hasGizmoUtility = true; + bool gizmoState = false; + bool iconState = false; + Component[] components = colliderManager.GetComponentsInChildren(); + List usedTypes = new List(); + foreach (var component in components) + { + // we only need to set the GizmoInfo once per Type so can discard further instances of that Type + if (!usedTypes.Contains(component.GetType())) { - if (cs != currentCollider && cs.name.StartsWith(symName)) + usedTypes.Add(component.GetType()); + if (GizmoUtility.TryGetGizmoInfo(component.GetType(), out GizmoInfo info)) { - if (type == SymmetricalUpdateType.Update) - { - cs.MirrorX(currentCollider); - cs.Update(); - } - else if (type == SymmetricalUpdateType.Reset) + if (colliderManager.gizmoNames.Contains(info.name)) { - cs.Reset(); + if (info.hasGizmo) + { + gizmoState = info.gizmoEnabled; + info.gizmoEnabled = false; + } + + if (info.hasIcon) + { + iconState = info.iconEnabled; + info.iconEnabled = false; + } + GizmoUtility.ApplyGizmoInfo(info); + + if (info.name == "CapsuleCollider") { cachedGizmoState.capsuleEnabled = gizmoState; } + else if (info.name == "Cloth") { cachedGizmoState.clothEnabled = gizmoState; } + else if (info.name == "SphereCollider") { cachedGizmoState.sphereEnabled = gizmoState; } + else if (info.name == "BoxCollider") { cachedGizmoState.boxEnabled = gizmoState; } + else if (info.name == "MagicaCapsuleCollider") { cachedGizmoState.magicaCapsuleEnabled = gizmoState; cachedGizmoState.magicaCapsuleIconEnabled = iconState; } + else if (info.name == "MagicaCloth") { cachedGizmoState.magicaClothEnabled = gizmoState; cachedGizmoState.magicaClothIconEnabled = iconState; } + else if (info.name == "MagicaSphereCollider") { cachedGizmoState.magicaSphereEnabled = gizmoState; cachedGizmoState.magicaSphereIconEnabled = iconState; } + else if (info.name == "MagicaPlaneCollider") { cachedGizmoState.magicaPlaneEnabled = gizmoState; cachedGizmoState.magicaPlaneIconEnabled = iconState; } } - else if (type == SymmetricalUpdateType.Fetch) - { - cs.FetchSettings(); - } - } + } } - } - - symName = null; - - if (name == "CC_Base_NeckTwist01_Capsule(1)") - { - symName = "CC_Base_NeckTwist01_Capsule(2)"; - } - else if (name == "CC_Base_NeckTwist01_Capsule(2)") - { - symName = "CC_Base_NeckTwist01_Capsule(1)"; - } + } +#endif + PhysicsSettingsStore.SaveGizmoState(colliderManager, cachedGizmoState); + } - if (!string.IsNullOrEmpty(symName)) + public void ResetGizmos() + { + cachedGizmoState = PhysicsSettingsStore.RecallGizmoState(colliderManager); + if (cachedGizmoState == null) return; + //ColliderManager.GizmoState state = colliderManager.cachedGizmoState; + SceneView.lastActiveSceneView.drawGizmos = cachedGizmoState.gizmosEnabled; + +#if UNITY_2022_1_OR_NEWER + bool gizmoState = false; + bool iconState = false; + Component[] components = colliderManager.GetComponentsInChildren(); + List usedTypes = new List(); + foreach (var component in components) { - foreach (ColliderSettings cs in colliderManager.settings) + if (!usedTypes.Contains(component.GetType())) { - if (cs != currentCollider && cs.name.StartsWith(symName)) + usedTypes.Add(component.GetType()); + if (GizmoUtility.TryGetGizmoInfo(component.GetType(), out GizmoInfo info)) { - if (type == SymmetricalUpdateType.Update) - { - cs.MirrorZ(currentCollider); - cs.Update(); - } - else if (type == SymmetricalUpdateType.Reset) - { - cs.Reset(); - } - else if (type == SymmetricalUpdateType.Fetch) + if (colliderManager.gizmoNames.Contains(info.name)) { - cs.FetchSettings(); + if (info.name == "CapsuleCollider") { gizmoState = cachedGizmoState.capsuleEnabled; } + else if (info.name == "Cloth") { gizmoState = cachedGizmoState.clothEnabled; } + else if (info.name == "SphereCollider") { gizmoState = cachedGizmoState.sphereEnabled; } + else if (info.name == "BoxCollider") { gizmoState = cachedGizmoState.boxEnabled; } + else if (info.name == "MagicaCapsuleCollider") { gizmoState = cachedGizmoState.magicaCapsuleEnabled; iconState = cachedGizmoState.magicaCapsuleIconEnabled; } + else if (info.name == "MagicaCloth") { gizmoState = cachedGizmoState.magicaClothEnabled; iconState = cachedGizmoState.magicaClothIconEnabled; } + else if (info.name == "MagicaSphereCollider") { gizmoState = cachedGizmoState.magicaSphereEnabled; iconState = cachedGizmoState.magicaSphereIconEnabled; } + else if (info.name == "MagicaPlaneCollider") { gizmoState = cachedGizmoState.magicaPlaneEnabled; iconState = cachedGizmoState.magicaPlaneIconEnabled; } + + if (info.hasGizmo) + { + info.gizmoEnabled = gizmoState; + } + + if (info.hasIcon) + { + info.iconEnabled = iconState; + } + GizmoUtility.ApplyGizmoInfo(info); } } } } - } - - private void SelectCurrentCollider(object sel) - { - currentCollider = (ColliderSettings)sel; - if (currentCollider != null) - { - CURRENT_COLLIDER_NAME = currentCollider.name; - } - } - } +#endif + } + } } diff --git a/Editor/Compute/RLBakeShader.compute b/Editor/Compute/RLBakeShader.compute index 11ebc14..f890d94 100644 --- a/Editor/Compute/RLBakeShader.compute +++ b/Editor/Compute/RLBakeShader.compute @@ -6,6 +6,7 @@ #pragma kernel RLHDRPMask #pragma kernel RLURPMetallicGloss #pragma kernel RLGradient +#pragma kernel RLMagicaWeightMap // #pragma kernel RLDiffuseBlend #pragma kernel RLMask @@ -228,6 +229,9 @@ float4 greenMaskR; float4 blueMaskR; float4 alphaMaskR; +TEX2D(WeightMap); +float threshold; + // Node functions // float4 BlendOverlay_float4(float4 Base, float4 Blend, float Opacity) @@ -491,6 +495,26 @@ void RLGradient(uint3 id : SV_DispatchThreadID) Result[id.xy] = float4(v, v, v, 1); } +[numthreads(1, 1, 1)] +void RLMagicaWeightMap(uint3 id : SV_DispatchThreadID) +{ + int w, h; + Result.GetDimensions(w, h); + float2 uv = GetUV(id.xy); + + float weight = SAMPLE(WeightMap, uv).r; + // float threshold; + + // red = static + float r = weight <= threshold ? 1 : 0; + // green = can move + float g = weight > threshold ? 1 : 0; + // blue = obey limit + float b = weight >= threshold ? weight : 0; + + Result[id.xy] = float4(r, g, b, 1); +} + // Generic Bake kernels // [numthreads(1, 1, 1)] diff --git a/Editor/ComputeBake.cs b/Editor/ComputeBake.cs index 5e5e285..3b7dd5e 100644 --- a/Editor/ComputeBake.cs +++ b/Editor/ComputeBake.cs @@ -84,6 +84,18 @@ public ComputeBake(UnityEngine.Object character, CharacterInfo info, string text importAssets = new List(); } + public static string BakeTexturesFolder(string fbxPath, string textureFolderOverride = null) + { + string characterName = Path.GetFileNameWithoutExtension(fbxPath); + string fbxFolder = Path.GetDirectoryName(fbxPath); + string bakeFolder = Util.CreateFolder(fbxFolder, BAKE_FOLDER); + string characterFolder = Util.CreateFolder(bakeFolder, characterName); + string texturesFolder = Util.CreateFolder(characterFolder, + string.IsNullOrEmpty(textureFolderOverride) ? TEXTURES_FOLDER : textureFolderOverride); + + return texturesFolder; + } + private static Vector2Int GetMaxSize(Texture2D a) { Vector2Int max = new Vector2Int(MIN_SIZE, MIN_SIZE); @@ -1979,6 +1991,27 @@ public Texture2D BakeGradientMap(string folder, string name) return null; } + public static Texture2D BakeMagicaWeightMap(Texture2D physXWeightMap, float threshold, Vector2Int size, string folder, string name) + { + ComputeBakeTexture bakeTarget = + new ComputeBakeTexture(size, folder, name, Importer.FLAG_ALPHA_DATA | + Importer.FLAG_READ_WRITE | + Importer.FLAG_UNCOMPRESSED); + + ComputeShader bakeShader = Util.FindComputeShader(COMPUTE_SHADER); + if (bakeShader) + { + int kernel = bakeShader.FindKernel("RLMagicaWeightMap"); + bakeTarget.Create(bakeShader, kernel); + bakeShader.SetTexture(kernel, "WeightMap", physXWeightMap); + bakeShader.SetFloat("threshold", threshold); + bakeShader.Dispatch(kernel, bakeTarget.width, bakeTarget.height, 1); + return bakeTarget.SaveAndReimport(); + } + + return null; + } + public Texture2D BakeBlenderDiffuseAlphaMap(Texture2D diffuse, Texture2D alpha, string folder, string name) { Vector2Int maxSize = GetMaxSize(diffuse, alpha); diff --git a/Editor/ComputeBakeTexture.cs b/Editor/ComputeBakeTexture.cs index 162ba77..21fd958 100644 --- a/Editor/ComputeBakeTexture.cs +++ b/Editor/ComputeBakeTexture.cs @@ -45,16 +45,12 @@ public ComputeBakeTexture(Vector2Int size, string folder, string name, int flags width = size.x; height = size.y; this.flags = flags; - //Debug.Log(QualitySettings.activeColorSpace); // RenderTextureReadWrite.sRGB/Linear is ignored: // on windows platforms it is always linear // on MacOS platforms it is always gamma renderTexture = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, IsRGB ? RenderTextureReadWrite.sRGB : RenderTextureReadWrite.Linear); - //Debug.Log(renderTexture.descriptor.colorFormat); - //Debug.Log(renderTexture.descriptor.graphicsFormat); - //Debug.Log(renderTexture.descriptor.sRGB); saveTexture = new Texture2D(width, height, TextureFormat.ARGB32, true, !IsRGB); folderPath = folder; textureName = name; @@ -105,6 +101,15 @@ private void ApplyImportSettings(string filePath) importer.mipMapsPreserveCoverage = false; } importer.mipmapFilter = TextureImporterMipFilter.BoxFilter; + if ((flags & Importer.FLAG_READ_WRITE) > 0) + { + importer.isReadable = true; + } + if ((flags & Importer.FLAG_UNCOMPRESSED) > 0) + { + importer.textureCompression = TextureImporterCompression.Uncompressed; + importer.compressionQuality = 0; + } importer.SaveAndReimport(); //AssetDatabase.WriteImportSettingsIfDirty(filePath); } diff --git a/Editor/Importer.cs b/Editor/Importer.cs index 3799f87..1f78881 100644 --- a/Editor/Importer.cs +++ b/Editor/Importer.cs @@ -63,6 +63,8 @@ public class Importer public const int FLAG_ALPHA_DATA = 32; public const int FLAG_HAIR_ID = 64; public const int FLAG_WRAP_CLAMP = 1024; + public const int FLAG_READ_WRITE = 2048; + public const int FLAG_UNCOMPRESSED = 2048; public const float MAX_SMOOTHNESS = 0.897f; public const float TRA_SPECULAR_SCALE = 0.2f; @@ -203,7 +205,7 @@ public Importer(CharacterInfo info) // fetch the character json export data. jsonData = info.JsonData; - if (info.MeshJsonData == null) Util.LogError("Unable to find Json mesh data!"); + if (jsonData == null) Util.LogError("Unable to find Json data!"); jsonPhysicsData = info.PhysicsJsonData; if (jsonPhysicsData == null) @@ -771,7 +773,6 @@ private Material CreateRemapMaterial(MaterialType materialType, Material sharedM if (templateMaterial) { Util.LogInfo(" Using template material: " + templateMaterial.name); - //Debug.Log("Copying from Material template: " + templateMaterial.name); if (templateMaterial.shader && templateMaterial.shader != remapMaterial.shader) remapMaterial.shader = templateMaterial.shader; remapMaterial.CopyPropertiesFromMaterial(templateMaterial); @@ -2484,7 +2485,11 @@ private void AddWrinkleManager(GameObject obj, SkinnedMeshRenderer smr, Material WrinkleManager wm = obj.AddComponent(); wm.headMaterial = mat; wm.skinnedMeshRenderer = smr; - float overallWeight = matJson.GetFloatValue("Wrinkle/WrinkleOverallWeight"); + float overallWeight = 1; + if (matJson.PathExists("Wrinkle/WrinkleOverallWeight")) + { + overallWeight = matJson.GetFloatValue("Wrinkle/WrinkleOverallWeight"); + } wm.BuildConfig(BuildWrinkleProps(matJson), overallWeight); } diff --git a/Editor/ImporterFeaturesWindow.cs b/Editor/ImporterFeaturesWindow.cs new file mode 100644 index 0000000..3ccebc9 --- /dev/null +++ b/Editor/ImporterFeaturesWindow.cs @@ -0,0 +1,289 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.Sprites; +using UnityEngine; +using UnityEngine.UIElements; + +namespace Reallusion.Import +{ + public class ImporterFeaturesWindow : EditorWindow + { + static ImporterFeaturesWindow importerFeaturesWindow = null; + static long lastClosedTime; + private CharacterInfo contextCharacter; + private CharacterInfo originalCharacter; + bool massProcessingValidate = false; + bool flagChanged = false; + + private ImporterWindow importerWindow; + private Styles windowStyles; + private float DROPDOWN_WIDTH = 260f; + private float INITIAL_DROPDOWN_HEIGHT = 160f; + private float LABEL_WIDTH = 200f; + private float SECTION_INDENT = 8f; + private float SUB_SECTION_INDENT = 18f; + + void OnEnable() + { + AssemblyReloadEvents.beforeAssemblyReload += Close; + hideFlags = HideFlags.DontSave; + } + + void OnDisable() + { + AssemblyReloadEvents.beforeAssemblyReload -= Close; + importerFeaturesWindow = null; + } + + void MassProcessingWindowValidate() + { + if (MassProcessingWindow.massProcessingWindow != null) + { + if (originalCharacter != null) + { + CharacterInfo workingCharacter = MassProcessingWindow.massProcessingWindow.workingList.Where(x => x.guid == contextCharacter.guid).FirstOrDefault(); + workingCharacter.settingsChanged = contextCharacter.ShaderFlags != originalCharacter.ShaderFlags; + MassProcessingWindow.massProcessingWindow.FilterDisplayedList(); + } + } + } + + public static bool ShowAtPosition(Rect buttonRect, CharacterInfo contextChar = null) + { + long nowMilliSeconds = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond; + bool justClosed = nowMilliSeconds < lastClosedTime + 50; + if (!justClosed) + { + Event.current.Use(); + if (importerFeaturesWindow == null) + importerFeaturesWindow = ScriptableObject.CreateInstance(); + else + { + importerFeaturesWindow.Cancel(); + return false; + } + + importerFeaturesWindow.Init(buttonRect, contextChar); + return true; + } + return false; + } + + void Init(Rect buttonRect, CharacterInfo contextChar = null) + { + // Has to be done before calling Show / ShowWithMode + buttonRect = GUIUtility.GUIToScreenRect(buttonRect); + + importerWindow = ImporterWindow.Current; + + if (contextChar != null) + { + contextCharacter = contextChar; + originalCharacter = ImporterWindow.ValidCharacters.Where(x => x.guid == contextChar.guid).FirstOrDefault(); + massProcessingValidate = true; + } + else + { + contextCharacter = importerWindow.Character; + } + + Vector2 windowSize = new Vector2(DROPDOWN_WIDTH, INITIAL_DROPDOWN_HEIGHT); + ShowAsDropDown(buttonRect, windowSize); + } + + void Cancel() + { + Close(); + GUI.changed = true; + GUIUtility.ExitGUI(); + } + + public class Styles + { + public GUIStyle listEvenBg; + public GUIStyle listOddBg; + public GUIStyle listLabel; + + public Styles() + { + listEvenBg = new GUIStyle("ObjectPickerResultsOdd"); + listEvenBg.fontStyle = FontStyle.Normal; + + listOddBg = new GUIStyle("ObjectPickerResultsEven"); + listOddBg.fontStyle = FontStyle.Normal; + + listLabel = new GUIStyle("label"); + listLabel.fontSize = 12; + listLabel.fontStyle = FontStyle.Italic; + } + } + + void OnGUI() + { + if (windowStyles == null) windowStyles = new Styles(); + int line = 0; // used to determine the background tint of alternate lines to avoid a block of solid color + + flagChanged = false; + + GUILayout.BeginVertical(); + // manipulate the "[Flags]enum ShaderFeatures" with condidions on what flags are available + // due to pipleine version and available add-ons such as magica cloth or dynamic bone + // much more flexible than EditorGUILayout.EnumFlagsField + + DrawLabelLine(line++, "Material Shader Features:"); + + if (DrawFlagSelectionLine(line++, CharacterInfo.ShaderFeatureFlags.Tessellation, "", SECTION_INDENT)) + flagChanged = true; + + if (DrawFlagSelectionLine(line++, CharacterInfo.ShaderFeatureFlags.WrinkleMaps, "", SECTION_INDENT)) + flagChanged = true; + + DrawLabelLine(line++, "Character Physics:"); + + // Cloth Physics + if (DrawFlagSelectionLine(line++, CharacterInfo.ShaderFeatureFlags.ClothPhysics, "Enable Cloth Physics", SECTION_INDENT)) + flagChanged = true; + + if (importerWindow.MagicaCloth2Available) // cloth alternatives available so enable non default selections + { + if (contextCharacter.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.ClothPhysics)) + { + if (DrawFlagSelectionLine(line++, CharacterInfo.ShaderFeatureFlags.UnityClothPhysics, "Unity Cloth", SUB_SECTION_INDENT, CharacterInfo.clothGroup)) + flagChanged = true; + + if (importerWindow.MagicaCloth2Available) + { + if (DrawFlagSelectionLine(line++, CharacterInfo.ShaderFeatureFlags.MagicaCloth, "Magica Cloth 2", SUB_SECTION_INDENT, CharacterInfo.clothGroup)) + flagChanged = true; + } + } + } + + // Hair Physics + if (DrawFlagSelectionLine(line++, CharacterInfo.ShaderFeatureFlags.HairPhysics, "Enable Hair Physics", SECTION_INDENT)) + flagChanged = true; + + if (importerWindow.DynamicBoneAvailable || importerWindow.MagicaCloth2Available) // cloth/bone alternatives available so enable non default selections + { + if (contextCharacter.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.HairPhysics)) + { + if (DrawFlagSelectionLine(line++, CharacterInfo.ShaderFeatureFlags.UnityClothHairPhysics, "Unity Hair Physics", SUB_SECTION_INDENT, CharacterInfo.hairGroup)) + flagChanged = true; + + if (importerWindow.DynamicBoneAvailable) + { + if (DrawFlagSelectionLine(line++, CharacterInfo.ShaderFeatureFlags.SpringBoneHair, "Dynamic Bone Springbones", SUB_SECTION_INDENT, CharacterInfo.hairGroup)) + flagChanged = true; + } + if (importerWindow.MagicaCloth2Available) + { + if (DrawFlagSelectionLine(line++, CharacterInfo.ShaderFeatureFlags.MagicaBone, "Magica Bone Springbones", SUB_SECTION_INDENT, CharacterInfo.hairGroup)) + flagChanged = true; + } + } + } + + if (Event.current.type == EventType.Repaint) + { + minSize = new Vector2(DROPDOWN_WIDTH, GUILayoutUtility.GetLastRect().yMax); + } + + if (massProcessingValidate && flagChanged) + { + MassProcessingWindowValidate(); + } + } + + private bool DrawFlagSelectionLine(int line, CharacterInfo.ShaderFeatureFlags flag, string overrideLabel = "", float indent = 0f, CharacterInfo.ShaderFeatureFlags [] radioGroup = null) + { + GUILayout.BeginHorizontal(GetLineStyle(line)); + GUILayout.Space(indent); + bool flagVal = contextCharacter.ShaderFlags.HasFlag(flag); + GUILayout.Label(new GUIContent(string.IsNullOrEmpty(overrideLabel) ? flag.ToString() : overrideLabel, ""), GUILayout.Width(LABEL_WIDTH)); + EditorGUI.BeginChangeCheck(); + flagVal = GUILayout.Toggle(flagVal, ""); + if (EditorGUI.EndChangeCheck()) + { + if (radioGroup != null) + SetFeatureFlagInGroup(flag, radioGroup); + else + SetFeatureFlag(flag, flagVal); + + return true; + } + GUILayout.EndHorizontal(); + return false; + } + + private void DrawLabelLine(int line, string label) + { + GUILayout.BeginHorizontal(GetLineStyle(line)); + GUILayout.Label(label, windowStyles.listLabel); + GUILayout.EndHorizontal(); + } + + private GUIStyle GetLineStyle(int itemIndex) + { + if (windowStyles == null) windowStyles = new Styles(); + + return itemIndex % 2 > 0 ? windowStyles.listEvenBg : windowStyles.listOddBg; + } + + private void SetFeatureFlag(CharacterInfo.ShaderFeatureFlags flag, bool value) + { + if (value) + { + if (!contextCharacter.ShaderFlags.HasFlag(flag)) + { + contextCharacter.ShaderFlags |= flag; // toggle changed to ON => bitwise OR to add flag + contextCharacter.EnsureDefaultsAreSet(flag); + } + } + else + { + if (contextCharacter.ShaderFlags.HasFlag(flag)) + { + contextCharacter.ShaderFlags ^= flag; // toggle changed to OFF => bitwise XOR to remove flag + + // since the section flag is being unset then all the entries should be unset too + switch(flag) + { + case CharacterInfo.ShaderFeatureFlags.ClothPhysics: + { + if (contextCharacter.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.MagicaCloth)) + contextCharacter.ShaderFlags ^= CharacterInfo.ShaderFeatureFlags.MagicaCloth; + + if (contextCharacter.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.UnityClothPhysics)) + contextCharacter.ShaderFlags ^= CharacterInfo.ShaderFeatureFlags.UnityClothPhysics; + + break; + } + case CharacterInfo.ShaderFeatureFlags.HairPhysics: + { + if (contextCharacter.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.MagicaBone)) + contextCharacter.ShaderFlags ^= CharacterInfo.ShaderFeatureFlags.MagicaBone; + + if (contextCharacter.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.SpringBoneHair)) + contextCharacter.ShaderFlags ^= CharacterInfo.ShaderFeatureFlags.SpringBoneHair; + + if (contextCharacter.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.UnityClothHairPhysics)) + contextCharacter.ShaderFlags ^= CharacterInfo.ShaderFeatureFlags.UnityClothHairPhysics; + + break; + } + } + } + } + } + + private void SetFeatureFlagInGroup(CharacterInfo.ShaderFeatureFlags flag, CharacterInfo.ShaderFeatureFlags[] radioGroup) + { + foreach (CharacterInfo.ShaderFeatureFlags groupFlag in radioGroup) + { + SetFeatureFlag(groupFlag, groupFlag.Equals(flag)); + } + } + } +} diff --git a/Editor/ImporterFeaturesWindow.cs.meta b/Editor/ImporterFeaturesWindow.cs.meta new file mode 100644 index 0000000..d544505 --- /dev/null +++ b/Editor/ImporterFeaturesWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a00cbfbc9d6dad649818eb5b4bbd56e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/ImporterMenu.cs b/Editor/ImporterMenu.cs index 9534ba4..36129e0 100644 --- a/Editor/ImporterMenu.cs +++ b/Editor/ImporterMenu.cs @@ -20,6 +20,7 @@ using UnityEngine; using UnityEditor; +using System.IO; namespace Reallusion.Import { @@ -441,6 +442,25 @@ private static string WritePNG(string folderPath, Texture2D saveTexture, string if (File.Exists(filePath)) return assetPath; else return ""; } + + + [MenuItem("Reallusion/Dev/Magica Weight Map", priority = 220)] + public static void DoMagicaWeightMap() + { + CharacterInfo currentCharacter = ImporterWindow.Current.Character; + + string[] folders = new string[] { "Assets", "Packages" }; + Texture2D physXWeightMap = Util.FindTexture(folders, "physXWeightMapTest"); + + string folder = ComputeBake.BakeTexturesFolder(currentCharacter.path); + string name = "magicaWeightMapTest"; + float threshold = 1f / 255f; + Vector2Int size = new Vector2Int(64, 64); + // should create the texture in: /Baked//Textures + + Texture2D magicaWeightMap = ComputeBake.BakeMagicaWeightMap(physXWeightMap, threshold, size, folder, name); + } + #endif } } \ No newline at end of file diff --git a/Editor/ImporterWindow.cs b/Editor/ImporterWindow.cs index 76805ee..590cec6 100644 --- a/Editor/ImporterWindow.cs +++ b/Editor/ImporterWindow.cs @@ -134,6 +134,12 @@ public enum ImporterWindowMode { Build, Bake, Settings } //The TreeView is not serializable, so it should be reconstructed from the tree data. CharacterTreeView characterTreeView; + private bool magicaCloth2Available; + public bool MagicaCloth2Available { get { return magicaCloth2Available; } } + + private bool dynamicBoneAvailable; + public bool DynamicBoneAvailable { get { return dynamicBoneAvailable; } } + public static float ICON_AREA_WIDTH { get @@ -235,6 +241,8 @@ public void SetActiveCharacter(UnityEngine.Object obj, Mode mode) private void InitData() { + CheckAvailableAddons(); + string[] folders = new string[] { "Assets", "Packages" }; iconUnprocessed = Util.FindTexture(folders, "RLIcon_UnprocessedChar"); iconBasic = Util.FindTexture(folders, "RLIcon_BasicChar"); @@ -574,7 +582,7 @@ private void OnGUIInfoArea(Rect infoBlock) GUILayout.EndArea(); } - + Rect prev = new Rect(); private void OnGUIOptionArea(Rect optionBlock) { GUILayout.BeginArea(optionBlock); @@ -649,30 +657,49 @@ private void OnGUIOptionArea(Rect optionBlock) menu.ShowAsContext(); } - int features = 2; - if (Pipeline.isHDRP12) features++; // tessellation - if (Pipeline.is3D || Pipeline.isURP) features++; // Amplify - - if (features == 1) - { - contextCharacter.ShaderFlags = (CharacterInfo.ShaderFeatureFlags)EditorGUILayout.EnumPopup(contextCharacter.ShaderFlags); - } - else if (features > 1) + // /* + bool showDebugEnumPopup = false; + if (showDebugEnumPopup) { - EditorGUI.BeginChangeCheck(); - contextCharacter.ShaderFlags = (CharacterInfo.ShaderFeatureFlags)EditorGUILayout.EnumFlagsField(contextCharacter.ShaderFlags); - if (EditorGUI.EndChangeCheck()) + int features = 2; + if (Pipeline.isHDRP12) features++; // tessellation + if (Pipeline.is3D || Pipeline.isURP) features++; // Amplify + + if (features == 1) + { + contextCharacter.ShaderFlags = (CharacterInfo.ShaderFeatureFlags)EditorGUILayout.EnumPopup(contextCharacter.ShaderFlags); + } + else if (features > 1) { - if ((contextCharacter.ShaderFlags & CharacterInfo.ShaderFeatureFlags.SpringBoneHair) > 0 && - (contextCharacter.ShaderFlags & CharacterInfo.ShaderFeatureFlags.HairPhysics) > 0) + EditorGUI.BeginChangeCheck(); + contextCharacter.ShaderFlags = (CharacterInfo.ShaderFeatureFlags)EditorGUILayout.EnumFlagsField(contextCharacter.ShaderFlags); + if (EditorGUI.EndChangeCheck()) { - contextCharacter.ShaderFlags -= CharacterInfo.ShaderFeatureFlags.SpringBoneHair; + if ((contextCharacter.ShaderFlags & CharacterInfo.ShaderFeatureFlags.SpringBoneHair) > 0 && + (contextCharacter.ShaderFlags & CharacterInfo.ShaderFeatureFlags.HairPhysics) > 0) + { + contextCharacter.ShaderFlags -= CharacterInfo.ShaderFeatureFlags.SpringBoneHair; + } } } } + // */ EditorGUI.EndDisabledGroup(); //GUI.enabled = true; + ////////////// + + if (Event.current.type == EventType.Repaint) + prev = GUILayoutUtility.GetLastRect(); + + if (EditorGUILayout.DropdownButton( + content: new GUIContent("Features"), + focusType: FocusType.Passive)) + { + ImporterFeaturesWindow.ShowAtPosition(new Rect(prev.x, prev.y + 20f, prev.width, prev.height)); + } + ////////////// + GUILayout.Space(8f); //if (contextCharacter.BuiltBasicMaterials) GUI.enabled = false; @@ -1112,6 +1139,7 @@ private void OnGUISettingsArea(Rect settingsBlock) string label = "Log Everything"; if (Util.LOG_LEVEL == 0) label = "Log Errors Only"; if (Util.LOG_LEVEL == 1) label = "Log Warnings and Errors"; + if (Util.LOG_LEVEL == 2) label = "Log Messages"; if (EditorGUILayout.DropdownButton( content: new GUIContent(label), focusType: FocusType.Passive)) @@ -1119,7 +1147,8 @@ private void OnGUISettingsArea(Rect settingsBlock) GenericMenu menu = new GenericMenu(); menu.AddItem(new GUIContent("Log Errors Only"), Util.LOG_LEVEL == 0, LogOptionSelected, 0); menu.AddItem(new GUIContent("Log Warnings and Errors"), Util.LOG_LEVEL == 1, LogOptionSelected, 1); - menu.AddItem(new GUIContent("Log Everything"), Util.LOG_LEVEL == 2, LogOptionSelected, 2); + menu.AddItem(new GUIContent("Log Messages"), Util.LOG_LEVEL == 2, LogOptionSelected, 2); + menu.AddItem(new GUIContent("Log Everything"), Util.LOG_LEVEL == 3, LogOptionSelected, 3); menu.ShowAsContext(); } GUILayout.Space(ROW_SPACE); @@ -1822,5 +1851,14 @@ public void FixMeh() } } } + + private void CheckAvailableAddons() + { + // init simple bools for the GUI to use to avoid repeatedly iterating through + // AppDomain.CurrentDomain.GetAssemblies() -- ALWAYS make these checks before any reflection code + dynamicBoneAvailable = Physics.DynamicBoneIsAvailable(); + magicaCloth2Available = Physics.MagicaCloth2IsAvailable(); + } + } } diff --git a/Editor/LodSelectionWindow.cs b/Editor/LodSelectionWindow.cs index ab62826..bf58091 100644 --- a/Editor/LodSelectionWindow.cs +++ b/Editor/LodSelectionWindow.cs @@ -195,7 +195,6 @@ private void ContainerGUI() //GUI.Button(boxRect, new GUIContent(EditorGUIUtility.IconContent("HumanTemplate Icon").image, pwd), boxStyle); //GUI.Box(boxRect, new GUIContent("X", "x")); boxRect = GetNextBox(boxRect, xNum); - //Debug.Log(model.Value); } if (modelList.Count == 0) @@ -321,24 +320,13 @@ private void AssetToModelList(string bakedPathSuffix, string path, string guid, } GameObject o = (GameObject)AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)); - Object src = Util.FindRootPrefabAsset(o); - LODGroup lg = o.GetComponentInChildren(); - if (lg) - { - // bit of a hack not to show the LODGroup generated by the Lodifier: - // LODGroups generated by the main importer put the LODGroup and Animator on different objects - // the Lodifier puts them on the same object. - Animator anm = lg.GetComponentInChildren(); - if (anm.gameObject == lg.gameObject) lg = null; - } - - if (lg || Util.IsCC3Character(src)) - { - //modelDict.Add(guid, assetName); + Object src = Util.FindRootPrefabAsset(o); + + if (Util.IsCC3Character(src)) + { GridModel g = new GridModel(); g.Guid = guid; - g.Name = assetName; - //g.Icon = (Texture2D)AssetDatabase.GetCachedIcon(path); + g.Name = assetName; g.Icon = AssetPreview.GetAssetPreview(o); g.Selected = true; g.Baked = baked; @@ -402,13 +390,7 @@ private GridModel GetBakedSourceModel(GridModel bgm) { return bgm; } - } - - private Editor MakeEditor(string guid) - { - Object o = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid), typeof(Object)); - return Editor.CreateEditor(o); - } + } private string GetAssetPath(string name, string extension) { diff --git a/Editor/Lodify.cs b/Editor/Lodify.cs index 7f2a61b..bbab38e 100644 --- a/Editor/Lodify.cs +++ b/Editor/Lodify.cs @@ -29,11 +29,12 @@ public class Lodify private GameObject lodRoot; private LODGroup lodGroup; private string characterName; - private List lodObjects; + private List lodSortObjects; private List lodInstances; + private List toDelete; private int numLevels; private Dictionary boneMap; - private string folder; + private string folder; public struct LODObject { @@ -63,29 +64,27 @@ public GameObject MakeLODPrefab(Object[] objects, string name = "") { // determine character name and prefab folder path characterName = objects[0].name; - string prefabName = characterName + "_LODGroup.prefab"; + string prefabName = characterName + "_LOD.prefab"; if (!string.IsNullOrEmpty(name)) prefabName = name + ".prefab"; folder = Path.GetDirectoryName(AssetDatabase.GetAssetPath(objects[0])); // create LOD group and add lod instances - AddLODInstances(objects); + MakeLODInstances(objects); // process the LOD instances, separate and sort the lod characters - ProcessLODInstances(); + ProcessLODInstances(); // remap the bones and fill the LOD groups - if (lod0BoneRoot) + if (lodRoot && lod0BoneRoot) { + lodRoot.name = characterName; GenerateBoneMap(); FillLODGroups(); - } - - // finally copy the LOD0 animator settings - ProcessAnimators(); + } // Clean up CleanUp(); - + string prefabPath = Path.Combine(folder, prefabName); GameObject prefabAsset = PrefabUtility.SaveAsPrefabAsset(lodRoot, prefabPath); @@ -97,35 +96,44 @@ public GameObject MakeLODPrefab(Object[] objects, string name = "") return null; } - private void AddLODObject(GameObject lodObj, GameObject boneRootSearch = null) + private bool AddSortLODObject(GameObject lodLevelObj, GameObject boneRootSearch = null) { - if (boneRootSearch == null) boneRootSearch = lodObj; + if (boneRootSearch == null) boneRootSearch = lodLevelObj; LODObject lodObject = new LODObject() { - lodObject = lodObj, - polyCount = CountPolys(lodObj), + lodObject = lodLevelObj, + polyCount = CountPolys(lodLevelObj), boneRoot = FindBoneRoot(boneRootSearch.transform), }; - for (int i = 0; i < lodObjects.Count; i++) + + // unparent from original container + lodLevelObj.transform.parent = null; + + for (int i = 0; i < lodSortObjects.Count; i++) { - if (lodObject.polyCount == lodObjects[i].polyCount) + if (lodObject.polyCount == lodSortObjects[i].polyCount) { - Util.LogWarn("LOD level with same poly count detected: skipping " + lodObj.name); - return; + Util.LogWarn("LOD level with same poly count detected: skipping " + lodLevelObj.name); + return false; } - if (lodObject.polyCount > lodObjects[i].polyCount) + if (lodObject.polyCount > lodSortObjects[i].polyCount) { - lodObjects.Insert(i, lodObject); - return; + // insert largest first + lodSortObjects.Insert(i, lodObject); + return true; } } - lodObjects.Add(lodObject); + + // add smallest last + lodSortObjects.Add(lodObject); + return true; } private void ProcessLODInstances() { - lodObjects = new List(lodInstances.Count); + lodSortObjects = new List(lodInstances.Count); + toDelete = new List(); foreach (GameObject lodObj in lodInstances) { @@ -138,8 +146,8 @@ private void ProcessLODInstances() if (lodCount == 1) { - // add lod instance to polycount sorted lod objects - AddLODObject(lodObj); + // add lod character instance to polycount sorted lod objects + AddSortLODObject(lodObj); } else if (lodCount > 1) { @@ -157,66 +165,68 @@ private void ProcessLODInstances() string levelString = r.name.Substring(r.name.Length - 1, 1); if (int.TryParse(levelString, out int level)) { - // move this LOD level into it's own lodContainer (child of the LODGroup) - GameObject lodContainer = new GameObject(name + "_LOD" + level.ToString()); - lodContainer.transform.parent = lodRoot.transform; - lodContainer.transform.localPosition = Vector3.zero; - lodContainer.transform.localRotation = Quaternion.identity; - CopyAnimator(lodObj, lodContainer); - r.transform.parent = lodContainer.transform; - AddLODObject(lodContainer, lodObj); + // move this LOD level into it's own lodContainer and add to sorted lod objects + GameObject lodContainer = new GameObject(name + "_LOD" + level.ToString()); + r.transform.parent = lodContainer.transform; + AddSortLODObject(lodContainer, lodObj); } } else { // assume any mesh without a _LOD suffix is part of the original model (LOD0) - // move this LOD level into it's own lod0Container (child of the LODGroup) + // leave these meshes in their original container and use this as the + // LOD0 root container and unparent this from the instance. + // (this lod0Container should have the animator and any physics components) if (!lod0Container) { - lod0Container = new GameObject(name + "_LOD0"); - lod0Container.transform.parent = lodRoot.transform; - lod0Container.transform.localPosition = Vector3.zero; - lod0Container.transform.localRotation = Quaternion.identity; - CopyAnimator(lodObj, lod0Container); + Animator lodAnimator = lodObj.GetComponentInChildren(); + if (lodAnimator) + { + lod0Container = lodAnimator.gameObject; + if (lodAnimator.transform != r.transform.parent) + toDelete.Add(r.transform.parent.gameObject); + } } - r.transform.parent = lod0Container.transform; } } - // Add the LOD0 container + // Add the LOD0 container if found if (lod0Container) - { - AddLODObject(lod0Container, lodObj); - } + { + AddSortLODObject(lod0Container, lodObj); + } } - } - numLevels = lodObjects.Count; + numLevels = lodSortObjects.Count; if (numLevels > 0) - { + { // fetch the LOD0 bone root - lod0BoneRoot = lodObjects[0].boneRoot; + lod0BoneRoot = lodSortObjects[0].boneRoot; + lodRoot = lodSortObjects[0].lodObject; - // move to lodRoot - lod0BoneRoot.parent = lodRoot.transform; + // parent to lodRoot if needed + if (lod0BoneRoot.parent != lodRoot.transform) + { + lod0BoneRoot.parent = lodRoot.transform; + } } } - private void AddLODInstances(Object[] objects) + private void MakeLODInstances(Object[] lodCharacters) { - lodRoot = new GameObject(characterName); - lodGroup = lodRoot.AddComponent(); - lodInstances = new List(objects.Length); + lodInstances = new List(lodCharacters.Length); - foreach (Object obj in objects) + if (lodCharacters.Length > 0) { - GameObject lodInstance = (GameObject)GameObject.Instantiate(obj, lodRoot.transform); - lodInstance.name = obj.name; - lodInstances.Add(lodInstance); - - } + foreach (Object lodCharacter in lodCharacters) + { + GameObject lodInstance = (GameObject)GameObject.Instantiate(lodCharacter); + lodInstance.name = lodCharacter.name; + lodInstances.Add(lodInstance); + } + } } private void CopyAnimator(GameObject from, GameObject to) @@ -233,11 +243,11 @@ private void CopyAnimator(GameObject from, GameObject to) toAnimator.updateMode = fromAnimator.updateMode; toAnimator.cullingMode = fromAnimator.cullingMode; } - } + } private void ProcessAnimators() { - GameObject lod0 = lodObjects[0].lodObject; + GameObject lod0 = lodSortObjects[0].lodObject; // find and copy the lod0 animator settings if (lod0) @@ -253,18 +263,20 @@ private void FillLODGroups() int totalPolys = 0; int processedPolys = 0; - foreach (LODObject lob in lodObjects) totalPolys += lob.polyCount; + foreach (LODObject lob in lodSortObjects) totalPolys += lob.polyCount; - foreach (LODObject lob in lodObjects) - { - GameObject lodInstance = lob.lodObject; + foreach (LODObject lob in lodSortObjects) + { List lodRenderers = new List(); - Renderer[] renderers = lodInstance.GetComponentsInChildren(); + Renderer[] renderers = lob.lodObject.GetComponentsInChildren(); foreach (Renderer r in renderers) { RemapLODMesh(r.gameObject); lodRenderers.Add(r); - r.gameObject.transform.parent = lodRoot.transform; + if (r.gameObject.transform.parent != lodRoot.transform) + { + r.gameObject.transform.parent = lodRoot.transform; + } } processedPolys += lob.polyCount; // distribute transition sizes by the square root of processed polygon density @@ -274,6 +286,8 @@ private void FillLODGroups() level++; } + lodGroup = lodRoot.GetComponent(); + if (lodGroup == null) lodGroup = lodRoot.AddComponent(); lodGroup.SetLODs(lods); } @@ -315,17 +329,19 @@ private Transform GetBoneMapping(Transform lodBone) private void RemapLODMesh(GameObject gameObject) { - SkinnedMeshRenderer smr = gameObject.GetComponent(); + SkinnedMeshRenderer smr = gameObject.GetComponent(); if (smr) { + Transform rootBone = smr.rootBone; Transform[] newBones = new Transform[smr.bones.Length]; for (int i = 0; i < smr.bones.Length; ++i) { newBones[i] = GetBoneMapping(smr.bones[i]); } smr.bones = newBones; - } + smr.rootBone = GetBoneMapping(rootBone); + } } public static int CountPolys(GameObject asset) @@ -353,14 +369,19 @@ public static int CountPolys(GameObject asset) private void CleanUp() { // remove all the old lod object containers - foreach (LODObject lob in lodObjects) + foreach (LODObject lob in lodSortObjects) { - GameObject.DestroyImmediate(lob.lodObject); + if (lob.lodObject != lodRoot) GameObject.DestroyImmediate(lob.lodObject); } foreach (GameObject obj in lodInstances) { - GameObject.DestroyImmediate(obj); + if (obj != lodRoot) GameObject.DestroyImmediate(obj); + } + + foreach (GameObject obj in toDelete) + { + if (obj != lodRoot) GameObject.DestroyImmediate(obj); } } } diff --git a/Editor/MassProcessingWindow.cs b/Editor/MassProcessingWindow.cs index 4266e18..c92aeab 100644 --- a/Editor/MassProcessingWindow.cs +++ b/Editor/MassProcessingWindow.cs @@ -27,6 +27,7 @@ namespace Reallusion.Import { public class MassProcessingWindow : EditorWindow { + public static MassProcessingWindow massProcessingWindow; enum WindowMode { standard, extended } public enum DragBarId { a, b } public enum SortType { ascending, descending } @@ -82,9 +83,11 @@ public enum FilterType { all, processed, unprocessed } private Texture2D iconFilterRemove; private Texture2D iconRefreshList; + Rect prev = new Rect(); + private bool initDone = false; private ImporterWindow importerWindow; - List workingList; + public List workingList; List displayList; CharacterInfo characterSettings; private bool isMassSelected = false; @@ -94,7 +97,7 @@ public enum FilterType { all, processed, unprocessed } [MenuItem("Reallusion/Processing Tools/Batch Processing", priority = 400)] public static void ATInitAssetProcessing() { - OpenProcessingWindow(); + massProcessingWindow = OpenProcessingWindow(); } [MenuItem("Reallusion/Processing Tools/Batch Processing", true)] @@ -919,21 +922,38 @@ private void OnGUIExtendedSettingsArea(Rect optionBlock) menu.AddItem(new GUIContent("MSAA Coverage Hair"), characterSettings.CoverageHair, HairOptionSelected, CharacterInfo.HairQuality.Coverage); menu.ShowAsContext(); } - - int features = 2; - if (Pipeline.isHDRP12) features++; // tessellation - if (Pipeline.is3D || Pipeline.isURP) features++; // Amplify - EditorGUI.BeginChangeCheck(); - if (features == 1) - characterSettings.ShaderFlags = (CharacterInfo.ShaderFeatureFlags)EditorGUILayout.EnumPopup(characterSettings.ShaderFlags); - else if (features > 1) - characterSettings.ShaderFlags = (CharacterInfo.ShaderFeatureFlags)EditorGUILayout.EnumFlagsField(characterSettings.ShaderFlags); - if (EditorGUI.EndChangeCheck()) + // /* + bool showDebugEnumPopup = false; + if (showDebugEnumPopup) { - ValidateSettings(characterSettings); + int features = 2; + if (Pipeline.isHDRP12) features++; // tessellation + if (Pipeline.is3D || Pipeline.isURP) features++; // Amplify + EditorGUI.BeginChangeCheck(); + if (features == 1) + characterSettings.ShaderFlags = (CharacterInfo.ShaderFeatureFlags)EditorGUILayout.EnumPopup(characterSettings.ShaderFlags); + else if (features > 1) + characterSettings.ShaderFlags = (CharacterInfo.ShaderFeatureFlags)EditorGUILayout.EnumFlagsField(characterSettings.ShaderFlags); + if (EditorGUI.EndChangeCheck()) + { + ValidateSettings(characterSettings); + } + GUI.enabled = true; } - GUI.enabled = true; + // */ + ////////////// + + if (Event.current.type == EventType.Repaint) + prev = GUILayoutUtility.GetLastRect(); + if (EditorGUILayout.DropdownButton( + content: new GUIContent("Features"), + focusType: FocusType.Passive)) + { + ImporterFeaturesWindow.ShowAtPosition(new Rect(prev.x, prev.y + 20f, prev.width, prev.height), characterSettings); + } + ////////////// + /// GUILayout.Space(8f); if (characterSettings.BuiltBasicMaterials) GUI.enabled = false; @@ -1027,8 +1047,7 @@ private void ToggleSelectAllDisplayed() } } - - private bool ValidateSettings(CharacterInfo characterSettings, bool refresh = true) + public bool ValidateSettings(CharacterInfo characterSettings, bool refresh = true) { bool dirty = false; if (characterSettings != null) @@ -1274,7 +1293,6 @@ public static Rect GetEditorApplicationWindowRect() { if (t.FullName.iContains("ContainerWindow")) { - //Debug.Log("Type: " + t.FullName + " Found In: " + assembly.FullName); var showModeField = t.GetField("m_ShowMode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); var positionProperty = t.GetProperty("position", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); if (showModeField != null && positionProperty != null) diff --git a/Editor/MeshUtil.cs b/Editor/MeshUtil.cs index e48add8..c4d8980 100644 --- a/Editor/MeshUtil.cs +++ b/Editor/MeshUtil.cs @@ -333,7 +333,6 @@ public static void PruneBlendShapes(Object obj) Vector3 deltaSum = Vector3.zero; for (int d = 0; d < srcMesh.vertexCount; d++) deltaSum += deltaVerts[d]; - //Debug.Log(name + ": deltaSum = " + deltaSum.ToString()); if (deltaSum.magnitude > 0.1f) dstMesh.AddBlendShapeFrame(name, frameWeight, deltaVerts, deltaNormals, deltaTangents); diff --git a/Editor/Physics.cs b/Editor/Physics.cs index 5fd9d50..cc3f95b 100644 --- a/Editor/Physics.cs +++ b/Editor/Physics.cs @@ -22,6 +22,9 @@ using System; using System.IO; using System.Reflection; +using System.Collections; +using Object = UnityEngine.Object; +using UnityEngine.Diagnostics; namespace Reallusion.Import { @@ -29,7 +32,7 @@ public class Physics { const float MAX_DISTANCE = 0.1f; const float MAX_PENETRATION = 0.02f; - const float WEIGHT_POWER = 1f; + const float WEIGHT_POWER = 1f; public enum PhysicsType { @@ -54,7 +57,7 @@ public enum ColliderAxis public class CollisionShapeData { public string name; - public string boneName; + public string boneName; public bool boneActive; public ColliderType colliderType; public ColliderAxis colliderAxis; @@ -66,7 +69,7 @@ public class CollisionShapeData public Vector3 scale; public float radius; public float length; - public Vector3 extent; + public Vector3 extent; public CollisionShapeData(string bone, string name, QuickJSON colliderJson) { @@ -112,7 +115,7 @@ public class SoftPhysicsData public float softRigidMargin; public bool selfCollision; public float selfMargin; - public float stiffnessFrequency; + public float stiffnessFrequency; public bool isHair; public SoftPhysicsData(string mesh, string material, QuickJSON softPhysicsJson, QuickJSON characterJson) @@ -121,7 +124,7 @@ public SoftPhysicsData(string mesh, string material, QuickJSON softPhysicsJson, materialName = material; isHair = false; QuickJSON objectMaterialJson = characterJson.GetObjectAtPath("Meshes/" + mesh + "/Materials/" + material); - if (objectMaterialJson != null && + if (objectMaterialJson != null && objectMaterialJson.PathExists("Custom Shader/Shader Name") && objectMaterialJson.GetStringValue("Custom Shader/Shader Name") == "RLHair") isHair = true; @@ -180,16 +183,28 @@ public static float PHYSICS_WEIGHT_MAP_DETECT_COLLIDER_THRESHOLD } } - // bones that can have DynamicBone spring bone colliders - private List springColliderBones = new List { - "CC_Base_Head", "CC_Base_Spine01", "CC_Base_NeckTwist01", "CC_Base_R_Upperarm", "CC_Base_R_Upperarm", + // bones that can have spring bone colliders + private List GetVaildSpringBoneColliders() + { + // <<>> + + // use a placeholder fixed list + return springColliderBones; + } + + private List springColliderBones = new List { + "CC_Base_Head", "CC_Base_Spine01", "CC_Base_NeckTwist01", "CC_Base_R_Upperarm", "CC_Base_L_Upperarm", }; - + private GameObject prefabInstance; private float modelScale = 0.01f; private bool addClothPhysics = false; + private bool addUnityClothPhysics = false; + private bool addMagicaClothPhysics = false; private bool addHairPhysics = false; + private bool addUnityHairPhysics = false; private bool addHairSpringBones = false; + private bool addMagicaHairSpringBones = false; private List boneColliders; private List softPhysics; @@ -201,9 +216,12 @@ public static float PHYSICS_WEIGHT_MAP_DETECT_COLLIDER_THRESHOLD private List textureFolders; private QuickJSON jsonData; private bool aPose; + private CharacterInfo characterInfo; + private const int MAGICA_WEIGHT_SIZE = 128; public Physics(CharacterInfo info, GameObject prefabInstance) { + characterInfo = info; this.prefabInstance = prefabInstance; boneColliders = new List(); softPhysics = new List(); @@ -215,8 +233,12 @@ public Physics(CharacterInfo info, GameObject prefabInstance) fbxFolder = info.folder; jsonData = info.JsonData; addClothPhysics = (info.ShaderFlags & CharacterInfo.ShaderFeatureFlags.ClothPhysics) > 0; + addUnityClothPhysics = (info.ShaderFlags & CharacterInfo.ShaderFeatureFlags.UnityClothPhysics) > 0; + addMagicaClothPhysics = (info.ShaderFlags & CharacterInfo.ShaderFeatureFlags.MagicaCloth) > 0; addHairPhysics = (info.ShaderFlags & CharacterInfo.ShaderFeatureFlags.HairPhysics) > 0; + addUnityHairPhysics = (info.ShaderFlags & CharacterInfo.ShaderFeatureFlags.UnityClothHairPhysics) > 0; addHairSpringBones = (info.ShaderFlags & CharacterInfo.ShaderFeatureFlags.SpringBoneHair) > 0; + addMagicaHairSpringBones = (info.ShaderFlags & CharacterInfo.ShaderFeatureFlags.MagicaBone) > 0; string fbmFolder = Path.Combine(fbxFolder, characterName + ".fbm"); string texFolder = Path.Combine(fbxFolder, "textures", characterName); textureFolders = new List() { fbmFolder, texFolder }; @@ -231,8 +253,8 @@ public Physics(CharacterInfo info, GameObject prefabInstance) private void ReadPhysicsJson(QuickJSON physicsJson, QuickJSON characterJson) { QuickJSON shapesJson = physicsJson.GetObjectAtPath("Collision Shapes"); - QuickJSON softPhysicsJson = physicsJson.GetObjectAtPath("Soft Physics/Meshes"); - + QuickJSON softPhysicsJson = physicsJson.GetObjectAtPath("Soft Physics/Meshes"); + if (shapesJson != null) { boneColliders.Clear(); @@ -259,13 +281,13 @@ private void ReadPhysicsJson(QuickJSON physicsJson, QuickJSON characterJson) foreach (MultiValue meshJson in softPhysicsJson.values) { - string meshName = meshJson.Key; + string meshName = meshJson.Key; QuickJSON physicsMaterialsJson = meshJson.ObjectValue.GetObjectAtPath("Materials"); if (physicsMaterialsJson != null) { foreach (MultiValue matJson in physicsMaterialsJson.values) { - string materialName = matJson.Key; + string materialName = matJson.Key; if (matJson.ObjectValue != null) softPhysics.Add(new SoftPhysicsData(meshName, materialName, matJson.ObjectValue, characterJson)); } @@ -276,11 +298,21 @@ private void ReadPhysicsJson(QuickJSON physicsJson, QuickJSON characterJson) public void AddPhysics(bool applyInstance) { - AddColliders(); +#if UNITY_2020_1_OR_NEWER + AddCollidersToPrefabContents(); +#else + AddCollidersToPrefabInstance(); +#endif AddCloth(); AddSpringBones(); if (applyInstance) PrefabUtility.ApplyPrefabInstance(prefabInstance, InteractionMode.AutomatedAction); + +#if UNITY_2022_3_OR_NEWER + // Below 2022.x UnityEditorInternal.ComponentUtility is more restrictive + // Reorder components within prefab test + ReorderComponentsOfPrefabInstance(); +#endif } public void RemoveAllPhysics() @@ -288,28 +320,60 @@ public void RemoveAllPhysics() Collider[] colliders = prefabInstance.GetComponentsInChildren(); } - private void AddColliders() + private bool MAGICA_CLOTH_AVAILABLE = false; + private bool DYNAMIC_BONE_AVAILABLE = false; + +#if UNITY_2020_1_OR_NEWER + // post 2020.1 version - the PrefabUtility class was updated for 2020.1 + // uses a disposable helper struct for automatically loading the contents of a Prefab file, saving the contents and unloading the contents again. + // see: https://docs.unity3d.com/ScriptReference/PrefabUtility.EditPrefabContentsScope.html + private void AddCollidersToPrefabContents() { - if (!addClothPhysics && !addHairPhysics && !addHairSpringBones) + MAGICA_CLOTH_AVAILABLE = MagicaCloth2IsAvailable(); + DYNAMIC_BONE_AVAILABLE = DynamicBoneIsAvailable(); + + // edit within the character prefab + using (var editingScope = new PrefabUtility.EditPrefabContentsScope(AssetDatabase.GetAssetPath(ImporterWindow.Current.Character.PrefabAsset))) { - ColliderManager existingColliderManager = prefabInstance.GetComponent(); - if (existingColliderManager != null) + var prefabRoot = editingScope.prefabContentsRoot; + PurgeAllPhysicsComponents(prefabRoot); + + if (characterInfo.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.ClothPhysics) || characterInfo.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.ClothPhysics)) { - foreach (Collider c in existingColliderManager.colliders) - { - GameObject.DestroyImmediate(c.gameObject); - } - Component.DestroyImmediate(existingColliderManager); + AddCollidersToPrefabRoot(prefabRoot); } - return; + } + } +#endif + // pre 2020.1 legacy version which includes MagicaCloth and DynamicBone + // the PrefabUtility class was updated for 2020.1 - see this for early discussion: + // https://forum.unity.com/threads/how-do-i-edit-prefabs-from-scripts.685711/#post-4591465 + // This method uses LoadPrefabContents which: "Loads a Prefab Asset at a given path into an isolated Scene and returns the root GameObject of the Prefab." + private void AddCollidersToPrefabInstance() + { + MAGICA_CLOTH_AVAILABLE = MagicaCloth2IsAvailable(); + DYNAMIC_BONE_AVAILABLE = DynamicBoneIsAvailable(); + + string currentPrefabAssetPath = AssetDatabase.GetAssetPath(ImporterWindow.Current.Character.PrefabAsset); + GameObject prefabRoot = PrefabUtility.LoadPrefabContents(currentPrefabAssetPath); + PurgeAllPhysicsComponents(prefabRoot); + + if (characterInfo.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.ClothPhysics) || characterInfo.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.ClothPhysics)) + { + AddCollidersToPrefabRoot(prefabRoot); } - GameObject parent = new GameObject(); - GameObject g; - Transform[] objects = prefabInstance.GetComponentsInChildren(); - Dictionary colliderLookup = new Dictionary(); + PrefabUtility.SaveAsPrefabAsset(prefabRoot, currentPrefabAssetPath, out bool success); + Util.LogDetail("Prefab Asset: " + currentPrefabAssetPath + (success ? " successfully saved." : " failed to save.")); + PrefabUtility.UnloadPrefabContents(prefabRoot); + } + + private void AddCollidersToPrefabRoot(GameObject prefabRoot) + { + Transform[] objects = prefabRoot.GetComponentsInChildren(true); + Dictionary colliderLookup = new Dictionary(); Dictionary existingLookup = new Dictionary(); - + // delegates Func FindBone = (boneName) => Array.Find(objects, o => o.name.Equals(boneName)); Func FindColliderObj = (colliderName, bone) => @@ -324,205 +388,225 @@ private void AddColliders() } return null; }; - + + GameObject parent = new GameObject("Temporary GameObject"); + parent.transform.SetParent(prefabRoot.transform, false); + + List validSpringBoneColliders = GetVaildSpringBoneColliders(); + foreach (CollisionShapeData collider in boneColliders) { string colliderName = collider.boneName + "_" + collider.name; - Transform bone = FindBone(collider.boneName); - Collider existingCollider = FindColliderObj(colliderName, bone); - - g = new GameObject(); + GameObject g = GetColliderGameObject(colliderName, objects); g.transform.SetParent(parent.transform); - g.name = colliderName; - Transform t = g.transform; t.position = collider.translation * modelScale; - t.rotation = collider.rotation; + t.rotation = collider.rotation; - if (collider.colliderType.Equals(ColliderType.Capsule)) - { - CapsuleCollider c = g.AddComponent(); + bool boneValid = validSpringBoneColliders.Contains(collider.boneName); + bool addFullColliderSet = true; // placeholder - c.direction = (int)collider.colliderAxis; - float radius = (collider.radius - collider.margin * PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; - c.radius = radius; - c.height = collider.length * modelScale + radius * 2f; - colliderLookup.Add(c, collider.boneName); - if (existingCollider) existingLookup.Add(c, existingCollider); + if (addMagicaClothPhysics) + { + if (MAGICA_CLOTH_AVAILABLE) + { + Object c = AddMagicaCloth2Collider(g, collider); + colliderLookup.Add(c, collider.boneName); + } } - else if (collider.colliderType.Equals(ColliderType.Sphere)) + else if ((addMagicaHairSpringBones && boneValid) || (addMagicaHairSpringBones && addFullColliderSet)) { - CapsuleCollider c = g.AddComponent(); + if (MAGICA_CLOTH_AVAILABLE) + { + Object c = AddMagicaCloth2Collider(g, collider); + colliderLookup.Add(c, collider.boneName); + } + } - c.direction = (int)collider.colliderAxis; - float radius = (collider.radius - collider.margin * PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; - c.radius = radius; - c.height = 0f; + if (addUnityClothPhysics) + { + Object c = AddNativeCollider(g, collider); colliderLookup.Add(c, collider.boneName); - if (existingCollider) existingLookup.Add(c, existingCollider); } - else + else if ((addUnityHairPhysics && boneValid) || (addUnityHairPhysics && addFullColliderSet)) { - //BoxCollider b = g.gameObject.AddComponent(); - //b.size = collider.extent * modelScale; - //colliderLookup.Add(b, collider.boneName); + Object c = AddNativeCollider(g, collider); + colliderLookup.Add(c, collider.boneName); + } - CapsuleCollider c = g.AddComponent(); - c.direction = (int)collider.colliderAxis; - float radius; - float height; - switch (collider.colliderAxis) - { - case ColliderAxis.X: - radius = (collider.extent.y + collider.extent.z) / 4f; - height = collider.extent.x; - break; - case ColliderAxis.Z: - radius = (collider.extent.x + collider.extent.y) / 4f; - height = collider.extent.z; - break; - case ColliderAxis.Y: - default: - radius = (collider.extent.x + collider.extent.z) / 4f; - height = collider.extent.y; - break; - } - c.radius = (radius - collider.margin * PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; - c.height = height * modelScale; + if (addHairSpringBones && boneValid) + { + Object c = AddDynamicBoneCollider(g, collider); colliderLookup.Add(c, collider.boneName); - if (existingCollider) existingLookup.Add(c, existingCollider); - } + } } + // rotate all the transform data from its original space (JSON) into the Unity coordinate system parent.transform.Rotate(Vector3.left, 90); parent.transform.localScale = new Vector3(-1f, 1f, 1f); if (aPose) FixColliderAPose(objects, colliderLookup); // as the transforms have moved, need to re-sync the transforms in the physics engine - UnityEngine.Physics.SyncTransforms(); + UnityEngine.Physics.SyncTransforms(); - List listColliders = new List(colliderLookup.Count); + List listColliders = new List(colliderLookup.Count); - // revert all existing prefabs overrides... - foreach (KeyValuePair collPair in existingLookup) + foreach (KeyValuePair collPair in colliderLookup) { - PrefabUtility.RevertObjectOverride(collPair.Value, InteractionMode.UserAction); - } + Component c = collPair.Key as Component; + if (c) + { + Transform t = c.transform; - foreach (KeyValuePair collPair in colliderLookup) - { - Collider transformedCollider = collPair.Key; - string colliderBone = collPair.Value; + string colliderBone = collPair.Value; - Transform bone = FindBone(colliderBone); - if (bone) - { - if (existingLookup.TryGetValue(transformedCollider, out Collider existingCollider)) - { - // reparent with keep position - transformedCollider.transform.SetParent(bone, true); - // copy the transformed collider to the existing if they match - CopyCollider(transformedCollider, existingCollider); - // delete the transformed collider - GameObject.DestroyImmediate(transformedCollider.gameObject); - // add to list of colliders - listColliders.Add(existingCollider); - } - else + Transform bone = FindBone(colliderBone); + if (bone) { // reparent with keep position - transformedCollider.transform.SetParent(bone, true); + t.transform.SetParent(bone, true); // add to list of colliders - listColliders.Add(transformedCollider); + listColliders.Add(t); } } } - + GameObject.DestroyImmediate(parent); + // add collider manager to prefab root - ColliderManager colliderManager = prefabInstance.GetComponent(); - if (colliderManager == null) colliderManager = prefabInstance.AddComponent(); + ColliderManager colliderManager = prefabRoot.GetComponent(); + if (colliderManager == null) colliderManager = prefabRoot.AddComponent(); // add colliders to manager if (colliderManager) { colliderManager.characterGUID = characterGUID; + colliderManager.magicaCloth2Available = MAGICA_CLOTH_AVAILABLE; + colliderManager.dynamicBoneAvailable = DYNAMIC_BONE_AVAILABLE; colliderManager.AddColliders(listColliders); } - Type dynamicBoneColliderType = GetTypeInAssemblies("DynamicBoneCollider"); + SaveReferenceAbstractColliders(colliderManager); + } - if (addHairSpringBones) + public GameObject GetColliderGameObject(string colliderName, Transform[] prefabObjects) + { + // if there is an existing matching colider object inside the prefab then + // clean the native, magica and dynamic bone colliders and return the gameobject + // this should preserve any references to the collider gameobject + foreach (Transform childtransform in prefabObjects) { - if (dynamicBoneColliderType == null) - { - Debug.LogWarning("Warning: DynamicBone not found in project assembly."); - } - else + if (childtransform.name == colliderName) { - foreach (CollisionShapeData collider in boneColliders) + GameObject colliderGameObject = childtransform.gameObject; + + // if any collider objects have been disabled then re-enable them for rebuild + if (!colliderGameObject.activeInHierarchy) + colliderGameObject.SetActive(true); + + if (MAGICA_CLOTH_AVAILABLE) { - if (springColliderBones.Contains(collider.boneName)) + var magicaColliderType = GetTypeInAssemblies("MagicaCloth2.MagicaCapsuleCollider"); + if (magicaColliderType != null) { - string colliderName = collider.boneName + "_" + collider.name; - Transform bone = FindBone(collider.boneName); - Collider existingCollider = FindColliderObj(colliderName, bone); - - if (existingCollider && existingCollider.GetType() == typeof(CapsuleCollider)) + var mCol = colliderGameObject.GetComponent(magicaColliderType); + if (mCol) { - CapsuleCollider cc = (CapsuleCollider)existingCollider; - - var dynamicBoneColliderComponent = existingCollider.gameObject.GetComponent(dynamicBoneColliderType); - if (dynamicBoneColliderComponent == null) - { - dynamicBoneColliderComponent = existingCollider.gameObject.AddComponent(dynamicBoneColliderType); - } + GameObject.DestroyImmediate(mCol); + } + } + } - SetTypeField(dynamicBoneColliderType, dynamicBoneColliderComponent, "m_Height", cc.height); - SetTypeField(dynamicBoneColliderType, dynamicBoneColliderComponent, "m_Radius", cc.radius); - SetTypeField(dynamicBoneColliderType, dynamicBoneColliderComponent, "m_Center", cc.center); - SetTypeField(dynamicBoneColliderType, dynamicBoneColliderComponent, "m_Direction", cc.direction); + if (DYNAMIC_BONE_AVAILABLE) + { + var dynamicBoneColliderType = GetTypeInAssemblies("DynamicBoneCollider"); + if (dynamicBoneColliderType != null) + { + var dCol = colliderGameObject.GetComponent(dynamicBoneColliderType); + if (dCol) + { + GameObject.DestroyImmediate(dCol); } } } + + var col = colliderGameObject.GetComponent(); + if (col != null) + { + GameObject.DestroyImmediate(col); + } + return colliderGameObject; } } - - GameObject.DestroyImmediate(parent); + return new GameObject(colliderName); } - private bool CopyCollider(Collider from, Collider to) + private void PurgeAllPhysicsComponents(GameObject prefabRoot) { - if (from.GetType() != to.GetType()) return false; + Type dynamicBoneType = GetTypeInAssemblies("DynamicBone"); + Type magicaClothType = GetTypeInAssemblies("MagicaCloth2.MagicaCloth"); - if (from.GetType() == typeof(CapsuleCollider)) - { - CapsuleCollider ccFrom = (CapsuleCollider)from; - CapsuleCollider ccTo = (CapsuleCollider)to; - ccTo.direction = ccFrom.direction; - ccTo.radius = ccFrom.radius; - ccTo.height = ccFrom.height; - ccTo.center = ccFrom.center; - ccTo.transform.SetPositionAndRotation(ccFrom.transform.position, ccFrom.transform.rotation); - ccTo.transform.localScale = ccFrom.transform.localScale; - ccTo.enabled = ccFrom.enabled; - return true; - } - else if (from.GetType() == typeof(SphereCollider)) + Transform[] objects = prefabRoot.GetComponentsInChildren(); + foreach (Transform t in objects) { - SphereCollider ccFrom = (SphereCollider)from; - SphereCollider ccTo = (SphereCollider)to; - ccTo.radius = ccFrom.radius; - ccTo.center = ccFrom.center; - ccTo.transform.SetPositionAndRotation(ccFrom.transform.position, ccFrom.transform.rotation); - ccTo.transform.localScale = ccFrom.transform.localScale; - ccTo.enabled = ccFrom.enabled; - return true; + GameObject g = t.gameObject; + var clothInstance = g.GetComponent(); + if (clothInstance != null) + { + GameObject.DestroyImmediate(clothInstance); + } + + var weightMapperInstance = g.GetComponent(); + if (weightMapperInstance != null) + { + GameObject.DestroyImmediate(weightMapperInstance); + } + + var colliderManagerInstance = g.GetComponent(); + if (colliderManagerInstance != null) + { + GameObject.DestroyImmediate(colliderManagerInstance); + } + + if (magicaClothType != null) + { + var magicaClothInstance = g.GetComponent(magicaClothType); + if (magicaClothInstance != null) + { + GameObject.DestroyImmediate(magicaClothInstance); + } + } + + if (dynamicBoneType != null) + { + var dynamicBoneInstance = g.GetComponent(dynamicBoneType); + if (dynamicBoneInstance != null) + { + GameObject.DestroyImmediate(dynamicBoneInstance); + } + } } + } - return false; + public void SaveReferenceAbstractColliders(ColliderManager colliderManager) + { + // create a reference list of abstract colliders to be used as a 'reset to defaults' resource + CreateAbstractColliders(colliderManager, out List abstractColliders); + PhysicsSettingsStore.SaveAbstractColliderSettings(colliderManager, abstractColliders, true); } private void AddSpringBones() + { + if (addHairPhysics) + { + if (addHairSpringBones) + AddDynamicBoneSpringBones(); + + if (addMagicaHairSpringBones) + AddMagicaBoneCloth(); + } + } + + private void AddDynamicBoneSpringBones() { Type dynamicBoneType = GetTypeInAssemblies("DynamicBone"); @@ -580,7 +664,7 @@ private void AddSpringBones() if (hairRoots.Count > 0) { Util.LogInfo("Found: " + hairRoots.Count + " spring bone chains"); - SetTypeField(dynamicBoneType, dynamicBoneComponent, "m_Roots", hairRoots); + SetTypeField(dynamicBoneType, dynamicBoneComponent, "m_Roots", hairRoots); SetTypeField(dynamicBoneType, dynamicBoneComponent, "m_Damping", 0.11f); SetTypeField(dynamicBoneType, dynamicBoneComponent, "m_Elasticity", 0.04f); SetTypeField(dynamicBoneType, dynamicBoneComponent, "m_Stiffness", 0.33f); @@ -604,19 +688,12 @@ private void AddSpringBones() mCollidersClear.Invoke(colliders, null); - ColliderManager colliderManager = prefabInstance.GetComponent(); - if (colliderManager) + IList dynamicBoneColliders = FetchDynamicBoneColliders(prefabInstance, GetVaildSpringBoneColliders()); + foreach (var dynamicBoneCollider in dynamicBoneColliders) { - foreach (Collider c in colliderManager.colliders) - { - var dynamicBoneColliderComponent = c.gameObject.GetComponent(dynamicBoneColliderType); - if (dynamicBoneColliderComponent != null) - { - mCollidersAdd.Invoke(colliders, new object[] { dynamicBoneColliderComponent }); - } - } - } - } + mCollidersAdd.Invoke(colliders, new object[] { dynamicBoneCollider }); + } + } } /// See: https://stackoverflow.com/questions/10754150/dynamic-type-with-lists-in-c-sharp @@ -632,59 +709,73 @@ private void AddCloth() PrepWeightMaps(); - List hairMeshNames = FindHairMeshes(); - Transform[] transforms = prefabInstance.GetComponentsInChildren(); - foreach (Transform t in transforms) + if (MagicaCloth2IsAvailable() && addMagicaClothPhysics) AddMagicaMeshCloth(); + + if (addUnityClothPhysics || addUnityHairPhysics) { - GameObject obj = t.gameObject; - foreach (SoftPhysicsData data in softPhysics) + List hairMeshNames = FindHairMeshes(); + Transform[] transforms = prefabInstance.GetComponentsInChildren(); + foreach (Transform t in transforms) { - string meshName = obj.name; - if (meshName.iContains("_Extracted")) + GameObject obj = t.gameObject; + foreach (SoftPhysicsData data in softPhysics) { - meshName = meshName.Remove(meshName.IndexOf("_Extracted")); - } - - if (meshName == data.meshName) - { - if (CanAddPhysics(meshName, hairMeshNames)) + string meshName = obj.name; + if (meshName.iContains("_Extracted")) { - DoCloth(obj, meshName); - clothMeshes.Add(obj); + meshName = meshName.Remove(meshName.IndexOf("_Extracted")); } - else + + if (meshName == data.meshName) { - RemoveCloth(obj); + if (CanAddPhysics(meshName, hairMeshNames)) + { + if (!data.isHair && addUnityClothPhysics) + { + DoCloth(obj, meshName); + clothMeshes.Add(obj); + } + + if (data.isHair && addUnityHairPhysics) + { + DoCloth(obj, meshName); + clothMeshes.Add(obj); + } + } + else + { + RemoveCloth(obj); + } } } } - } - ColliderManager colliderManager = prefabInstance.GetComponent(); - if (colliderManager) - { - colliderManager.clothMeshes = clothMeshes.ToArray(); + ColliderManager colliderManager = prefabInstance.GetComponent(); + if (colliderManager) + { + colliderManager.clothMeshes = clothMeshes.ToArray(); + } } - } + } private bool CanAddPhysics(SoftPhysicsData data) { if (data != null) { if (data.isHair) - { + { if (addHairPhysics) return true; } else - { + { if (addClothPhysics) return true; } } return false; } - - private bool CanAddPhysics(string meshName, ListhairMeshNames) + + private bool CanAddPhysics(string meshName, List hairMeshNames) { if (hairMeshNames.iContains(meshName)) { @@ -694,7 +785,7 @@ private bool CanAddPhysics(string meshName, ListhairMeshNames) { return addClothPhysics; } - } + } private List FindHairMeshes() { @@ -713,7 +804,7 @@ private List FindHairMeshes() if (meshName == data.meshName) { - if (data.isHair && !hairMeshNames.Contains(meshName)) + if (data.isHair && !hairMeshNames.Contains(meshName)) hairMeshNames.Add(meshName); } } @@ -742,8 +833,8 @@ private void DoCloth(GameObject clothTarget, string meshName) SkinnedMeshRenderer renderer = clothTarget.GetComponent(); if (!renderer) return; Mesh mesh = renderer.sharedMesh; - if (!mesh) return; - + if (!mesh) return; + List settingsList = new List(); bool hasPhysics = false; @@ -753,7 +844,6 @@ private void DoCloth(GameObject clothTarget, string meshName) Material mat = renderer.sharedMaterials[i]; if (!mat) continue; - string sourceName = mat.name; if (sourceName.iContains("_2nd_Pass")) continue; if (sourceName.iContains("_1st_Pass")) @@ -762,7 +852,7 @@ private void DoCloth(GameObject clothTarget, string meshName) } foreach (SoftPhysicsData data in softPhysics) - { + { if (data.materialName == sourceName && data.meshName == meshName && CanAddPhysics(data)) @@ -820,117 +910,407 @@ private void DoCloth(GameObject clothTarget, string meshName) } } - public void RemoveCloth(GameObject obj) - { - Cloth cloth = obj.GetComponent(); - WeightMapper mapper = obj.GetComponent(); - - if (cloth) Component.DestroyImmediate(cloth); - if (mapper) Component.DestroyImmediate(mapper); - } - - private Texture2D GetTextureFrom(string jsonTexturePath, string materialName, string suffix, out string name, bool search) + private void AddMagicaMeshCloth() { - Texture2D tex = null; - name = ""; + // construct a single instance of magica cloth + var cloth = AddMagicaClothInstance(0); - // try to find the texture from the supplied texture path (usually from the json data). - if (!string.IsNullOrEmpty(jsonTexturePath)) + // add relevant skinned mesh renderers along with converted weight maps + // add a list of colliders that can interact with the cloth instance + Type clothType = GetTypeInAssemblies("MagicaCloth2.MagicaCloth"); + if (clothType != null) { - // try to load the texture asset directly from the json path. - tex = AssetDatabase.LoadAssetAtPath(Util.CombineJsonTexPath(fbxFolder, jsonTexturePath)); - name = Path.GetFileNameWithoutExtension(jsonTexturePath); - - // if that fails, try to find the texture by name in the texture folders. - if (!tex && search) + Transform[] transforms = prefabInstance.GetComponentsInChildren(); + foreach (Transform t in transforms) { - tex = Util.FindTexture(textureFolders.ToArray(), name); + GameObject obj = t.gameObject; + foreach (SoftPhysicsData data in softPhysics) + { + string meshName = obj.name; + // omit hair for the moment + if (!data.isHair) + { + if (meshName == data.meshName) + { + DoMagicaCloth(cloth, obj, data); + } + } + } } - } - - // as a final fallback try to find the texture from the material name and suffix. - if (!tex && search) - { - name = materialName + "_" + suffix; - tex = Util.FindTexture(textureFolders.ToArray(), name); - } - - return tex; - } - - private void SetWeightMapImport(Texture2D tex) - { - if (!tex) return; - // now fix the import settings for the texture. - string path = AssetDatabase.GetAssetPath(tex); - TextureImporter importer = (TextureImporter)AssetImporter.GetAtPath(path); - - importer.alphaSource = TextureImporterAlphaSource.None; - importer.mipmapEnabled = false; - importer.mipmapFilter = TextureImporterMipFilter.BoxFilter; - importer.sRGBTexture = true; - importer.alphaIsTransparency = false; - importer.textureType = TextureImporterType.Default; - importer.isReadable = true; - importer.textureCompression = TextureImporterCompression.Uncompressed; - importer.compressionQuality = 0; - importer.maxTextureSize = 2048; + IList magicaColliderList = FetchMagicaColliders(prefabInstance.gameObject); - AssetDatabase.WriteImportSettingsIfDirty(path); - } - - public static GameObject RebuildPhysics(CharacterInfo characterInfo) - { - GameObject prefabAsset = characterInfo.PrefabAsset; + var serializedDataProperty = cloth.GetType().GetProperty("SerializeData"); + var serializedData = serializedDataProperty.GetValue(cloth); - if (prefabAsset) - { - GameObject prefabInstance = (GameObject)PrefabUtility.InstantiatePrefab(prefabAsset); + var particleRadius = serializedData.GetType().GetField("radius"); + if (particleRadius != null) + { + var particleRadiusData = particleRadius.GetValue(serializedData); + var particleRadiusValueField = particleRadiusData.GetType().GetField("value"); + if (particleRadiusValueField != null) + { + particleRadiusValueField.SetValue(particleRadiusData, 0.01f); // set the particle radius -- helps avoid the collider pushing out the cloth + } + } - if (prefabAsset && prefabInstance && characterInfo.PhysicsJsonData != null) + var collisionConstraint = serializedData.GetType().GetField("colliderCollisionConstraint"); + if (collisionConstraint != null) { - characterInfo.ShaderFlags |= CharacterInfo.ShaderFeatureFlags.ClothPhysics; - Physics physics = new Physics(characterInfo, prefabInstance); - physics.AddPhysics(true); - characterInfo.Write(); + var collisionConstraintData = collisionConstraint.GetValue(serializedData); + if (collisionConstraintData != null) + { + var colliderListField = collisionConstraintData.GetType().GetField("colliderList"); + if (colliderListField != null) + { + var actualColliderList = colliderListField.GetValue(collisionConstraintData); + if (actualColliderList != null) + { + colliderListField.SetValue(collisionConstraintData, magicaColliderList); + } + } + } } - if (prefabInstance) GameObject.DestroyImmediate(prefabInstance); + MethodInfo setParameterChange = cloth.GetType().GetMethod("SetParameterChange"); + setParameterChange.Invoke(cloth, new object[] { }); } - - return prefabAsset; } - void FixColliderAPose(Transform[] objects, Dictionary colliderLookup) + private void DoMagicaCloth(Object cloth, GameObject obj, SoftPhysicsData data) { - Func FindBone = (boneName) => Array.Find(objects, o => o.name.iEquals(boneName)); - - Transform leftArm = FindBone("CC_Base_L_Upperarm"); - if (!leftArm) leftArm = FindBone("L_Upperarm"); - - Transform rightArm = FindBone("CC_Base_R_Upperarm"); - if (!leftArm) rightArm = FindBone("R_Upperarm"); + // needs a skinned mesh renderer, a weightmap and a list of magica colliders + var serializedDataProperty = cloth.GetType().GetProperty("SerializeData"); + var serializedData = serializedDataProperty.GetValue(cloth); - foreach(KeyValuePair pair in colliderLookup) + if (serializedData != null) { - Collider c = pair.Key; - string boneName = pair.Value; - Transform bone = FindBone(boneName); - if (bone) + var sourceRenderersField = serializedData.GetType().GetField("sourceRenderers"); + if (sourceRenderersField != null) { - if (bone == leftArm || bone.IsChildOf(leftArm)) - { - c.transform.RotateAround(leftArm.position, Vector3.forward, -30f); - } - else if (bone == rightArm || bone.IsChildOf(rightArm)) + SkinnedMeshRenderer smr = obj.GetComponent(); + if (smr != null) { - c.transform.RotateAround(rightArm.position, Vector3.forward, 30f); - } + var rendererList = sourceRenderersField.GetValue(serializedData); + List renderers; + + if (rendererList == null) + { + renderers = new List(); + } + else + { + renderers = (List)rendererList; + } + + renderers.Add(smr); + sourceRenderersField.SetValue(serializedData, renderers); + + var reductionSettingField = serializedData.GetType().GetField("reductionSetting"); + if (reductionSettingField != null) + { + var reductionsettingFieldValue = reductionSettingField.GetValue(serializedData); + SetTypeField(reductionsettingFieldValue.GetType(), reductionsettingFieldValue, "simpleDistance", 0.06f); + SetTypeField(reductionsettingFieldValue.GetType(), reductionsettingFieldValue, "shapeDistance", 0.06f); + } + + var paintModeField = serializedData.GetType().GetField("paintMode"); + if (paintModeField != null) + { + paintModeField.SetValue(serializedData, 1); //MagicaCloth2.ClothSerializeData.PaintMode.Texture_Fixed_Move + } + + var paintMapsField = serializedData.GetType().GetField("paintMaps"); + if (paintMapsField != null) + { + var currentPaintMaps = paintMapsField.GetValue(serializedData); + List paintMaps; + + if (currentPaintMaps != null) + { + paintMaps = currentPaintMaps as List; + } + else + { + paintMaps = new List(); + } + + Texture2D weightMap = ConvertWeightmap(data); + if (!weightMap) weightMap = Texture2D.blackTexture; + paintMaps.Add(weightMap); + + paintMapsField.SetValue(serializedData, paintMaps); + } + + MethodInfo setParameterChange = cloth.GetType().GetMethod("SetParameterChange"); + setParameterChange.Invoke(cloth, new object[] { }); + } } } } + private void AddMagicaBoneCloth() + { + // construct a single instance of magica BoneCloth + // TODO: This section needs to be reorganized to deal with multiple spring bone systems requiring inidividual paramaters i.e. multiple bonecloth instances + var cloth = AddMagicaClothInstance(1); + + Type clothType = GetTypeInAssemblies("MagicaCloth2.MagicaCloth"); + if (clothType != null) + { + IList magicaColliderList = FetchMagicaColliders(prefabInstance.gameObject, GetVaildSpringBoneColliders()); + + var serializedDataProperty = cloth.GetType().GetProperty("SerializeData"); + var serializedData = serializedDataProperty.GetValue(cloth); + + List springRigs = new List(); + MeshUtil.FindCharacterBones(prefabInstance, springRigs, "RL_Hair_Rig_", "RLS_"); + + List hairRoots = new List(); + // build all spring rigs + foreach (GameObject rigBone in springRigs) + { + Util.LogInfo("Processing Spring Bone Rig: " + rigBone.name); + for (int i = 0; i < rigBone.transform.childCount; i++) + { + Transform childBone = rigBone.transform.GetChild(i); + hairRoots.Add(childBone); + } + } + + if (hairRoots.Count > 0) + { + Util.LogInfo("Found: " + hairRoots.Count + " spring bone chains"); + + var rootBonesField = serializedData.GetType().GetField("rootBones"); + if (rootBonesField != null) + { + rootBonesField.SetValue(serializedData, hairRoots); + } + + var collisionConstraint = serializedData.GetType().GetField("colliderCollisionConstraint"); + if (collisionConstraint != null) + { + var collisionConstraintData = collisionConstraint.GetValue(serializedData); + if (collisionConstraintData != null) + { + var colliderListField = collisionConstraintData.GetType().GetField("colliderList"); + if (colliderListField != null) + { + var actualColliderList = colliderListField.GetValue(collisionConstraintData); + if (actualColliderList != null) + { + colliderListField.SetValue(collisionConstraintData, magicaColliderList); + } + } + } + } + } + MethodInfo setParameterChange = cloth.GetType().GetMethod("SetParameterChange"); + setParameterChange.Invoke(cloth, new object[] { }); + } + } + + private Object AddMagicaClothInstance(int typeValue) + { + // enum ClothProcess.ClothType MeshCloth = 0, BoneCloth = 1 + + CharacterInfo currentChar = ImporterWindow.Current.Character; + string fbxPath = currentChar.path; + ModelImporter importer = (ModelImporter)AssetImporter.GetAtPath(fbxPath); + if (importer != null) + { + if (!importer.isReadable) + { + importer.isReadable = true; + importer.SaveAndReimport(); + } + } + + GameObject g = prefabInstance.gameObject; + Type clothType = GetTypeInAssemblies("MagicaCloth2.MagicaCloth"); + if (clothType != null) + { + // add cloth component + var existingCloth = g.GetComponent(clothType); + if (existingCloth) + { + if (GetMagicaClothType(existingCloth) == typeValue) + { + GameObject.DestroyImmediate(existingCloth); // if its an existing instance of same type then destroy it + } + } + + var cloth = g.AddComponent(clothType); + + bool clothSet = SetMagicaClothType(cloth, typeValue); + return cloth; + } + return null; + } + + private int GetMagicaClothType(Object cloth) + { + // enum ClothProcess.ClothType MeshCloth = 0, BoneCloth = 1 + + var serializedDataProperty = cloth.GetType().GetProperty("SerializeData"); + var serializedData = serializedDataProperty.GetValue(cloth); + if (serializedData != null) + { + var clothTypeField = serializedData.GetType().GetField("clothType"); + if (clothTypeField != null) + { + var clothTypeData = clothTypeField.GetValue(serializedData); + + if (clothTypeData.ToString() == "MeshCloth") return 0; + if (clothTypeData.ToString() == "BoneCloth") return 1; + } + } + return -1; + } + + private bool SetMagicaClothType(Object cloth, int value) + { + // enum ClothProcess.ClothType MeshCloth = 0, BoneCloth = 1 + + var serializedDataProperty = cloth.GetType().GetProperty("SerializeData"); + var serializedData = serializedDataProperty.GetValue(cloth); + if (serializedData != null) + { + var clothTypeField = serializedData.GetType().GetField("clothType"); + if (clothTypeField != null) + { + clothTypeField.SetValue(serializedData, value); + return true; + } + } + return false; + } + + public void RemoveCloth(GameObject obj) + { + Cloth cloth = obj.GetComponent(); + if (cloth) Component.DestroyImmediate(cloth); + + WeightMapper mapper = obj.GetComponent(); + if (mapper) Component.DestroyImmediate(mapper); + + Type magicaClothType = GetTypeInAssemblies("MagicaCloth2.MagicaCloth"); + if (magicaClothType != null) + { + var magicaClothInstance = obj.GetComponent(magicaClothType); + if (magicaClothInstance != null) Component.DestroyImmediate(magicaClothInstance); + } + } + + private Texture2D GetTextureFrom(string jsonTexturePath, string materialName, string suffix, out string name, bool search) + { + Texture2D tex = null; + name = ""; + + // try to find the texture from the supplied texture path (usually from the json data). + if (!string.IsNullOrEmpty(jsonTexturePath)) + { + // try to load the texture asset directly from the json path. + tex = AssetDatabase.LoadAssetAtPath(Util.CombineJsonTexPath(fbxFolder, jsonTexturePath)); + name = Path.GetFileNameWithoutExtension(jsonTexturePath); + + // if that fails, try to find the texture by name in the texture folders. + if (!tex && search) + { + tex = Util.FindTexture(textureFolders.ToArray(), name); + } + } + + // as a final fallback try to find the texture from the material name and suffix. + if (!tex && search) + { + name = materialName + "_" + suffix; + tex = Util.FindTexture(textureFolders.ToArray(), name); + } + + return tex; + } + + private void SetWeightMapImport(Texture2D tex) + { + if (!tex) return; + + // now fix the import settings for the texture. + string path = AssetDatabase.GetAssetPath(tex); + TextureImporter importer = (TextureImporter)AssetImporter.GetAtPath(path); + + importer.alphaSource = TextureImporterAlphaSource.None; + importer.mipmapEnabled = false; + importer.mipmapFilter = TextureImporterMipFilter.BoxFilter; + importer.sRGBTexture = true; + importer.alphaIsTransparency = false; + importer.textureType = TextureImporterType.Default; + importer.isReadable = true; + importer.textureCompression = TextureImporterCompression.Uncompressed; + importer.compressionQuality = 0; + bool magica = characterInfo.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.MagicaCloth); + importer.maxTextureSize = magica ? MAGICA_WEIGHT_SIZE : 2048; + + AssetDatabase.WriteImportSettingsIfDirty(path); + } + + public static GameObject RebuildPhysics(CharacterInfo characterInfo) + { + GameObject prefabAsset = characterInfo.PrefabAsset; + + if (prefabAsset) + { + GameObject prefabInstance = (GameObject)PrefabUtility.InstantiatePrefab(prefabAsset); + + if (prefabAsset && prefabInstance && characterInfo.PhysicsJsonData != null) + { + //characterInfo.ShaderFlags |= CharacterInfo.ShaderFeatureFlags.ClothPhysics; + Physics physics = new Physics(characterInfo, prefabInstance); + physics.AddPhysics(true); + characterInfo.Write(); + } + + if (prefabInstance) GameObject.DestroyImmediate(prefabInstance); + } + + return prefabAsset; + } + + void FixColliderAPose(Transform[] objects, Dictionary colliderLookup) + { + Func FindBone = (boneName) => Array.Find(objects, o => o.name.iEquals(boneName)); + + Transform leftArm = FindBone("CC_Base_L_Upperarm"); + if (!leftArm) leftArm = FindBone("L_Upperarm"); + + Transform rightArm = FindBone("CC_Base_R_Upperarm"); + if (!leftArm) rightArm = FindBone("R_Upperarm"); + + foreach (KeyValuePair pair in colliderLookup) + { + Component c = pair.Key as Component; + if (c) + { + Transform t = c.transform; + string boneName = pair.Value; + Transform bone = FindBone(boneName); + if (bone) + { + if (bone == leftArm || bone.IsChildOf(leftArm)) + { + t.RotateAround(leftArm.position, Vector3.forward, -30f); + } + else if (bone == rightArm || bone.IsChildOf(rightArm)) + { + t.RotateAround(rightArm.position, Vector3.forward, 30f); + } + } + } + + } + } + public static System.Type GetTypeInAssemblies(string typeName) { Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); @@ -949,6 +1329,18 @@ public static System.Type GetTypeInAssemblies(string typeName) return null; } + public static bool GetTypeField(object o, string field, out object value) + { + FieldInfo fieldInfo = o.GetType().GetField(field); + if (fieldInfo != null) + { + value = fieldInfo.GetValue(o); + return true; + } + value = null; + return false; + } + public static bool SetTypeField(Type t, object o, string field, object value) { FieldInfo fRoots = t.GetField(field); @@ -959,5 +1351,500 @@ public static bool SetTypeField(Type t, object o, string field, object value) } return false; } + + public static bool GetTypeProperty(object o, string property, out object value) + { + PropertyInfo propertyInfo = o.GetType().GetProperty(property); + if (propertyInfo != null) + { + value = propertyInfo.GetValue(o); + return true; + } + value = null; + return false; + } + + public static bool SetTypeProperty(object o, string property, object value) + { + PropertyInfo propertyInfo = o.GetType().GetProperty(property); + if (propertyInfo != null) + { + propertyInfo.SetValue(o, value); + return true; + } + return false; + } + + public static bool CreateAbstractColliders(ColliderManager colliderManager, out List abstractColliders) + { + CharacterInfo current; + + if (ImporterWindow.Current != null) + { + // current live info (used for shaderflags) allows for switching between native and magica and rebuilding physics + current = ImporterWindow.Current.Character; + } + else + { + // contains shaderflags from last build - this is acceptable when this function is called from the collidermanager in the absence of an importer window + current = new CharacterInfo(colliderManager.characterGUID); + } + + bool native = current.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.UnityClothPhysics); + bool nativeHair = current.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.UnityClothHairPhysics); + bool magica = current.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.MagicaCloth) && MagicaCloth2IsAvailable(); + bool magicaBoneHair = current.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.MagicaBone) && MagicaCloth2IsAvailable(); + bool dynamic = current.ShaderFlags.HasFlag(CharacterInfo.ShaderFeatureFlags.SpringBoneHair) && DynamicBoneIsAvailable(); + abstractColliders = new List(); + + if (MagicaCloth2IsAvailable()) + { + colliderManager.magicaColliderType = GetTypeInAssemblies("MagicaCloth2.MagicaCapsuleCollider"); // very slow + } + + if (DynamicBoneIsAvailable()) + { + colliderManager.dynamicBoneColliderType = GetTypeInAssemblies("DynamicBoneCollider"); // very slow + } + // create an array of the in-scene transforms in the character hierarchy + Transform[] allChildTransforms = colliderManager.gameObject.GetComponentsInChildren(true); + foreach (Transform childtransform in allChildTransforms) + { + GameObject go = childtransform.gameObject; + if (colliderManager.TransformHasAnyValidCollider(childtransform)) + { + ColliderManager.AbstractCapsuleCollider abs = new ColliderManager.AbstractCapsuleCollider(); + + if (native || nativeHair) + { + if (go.GetComponent(typeof(CapsuleCollider))) + { + CapsuleCollider coll = go.GetComponent(); + abs.transform = coll.transform; + abs.isEnabled = coll.transform.gameObject.activeSelf; + abs.localPosition = coll.transform.localPosition; + abs.localRotation = coll.transform.localRotation; + abs.height = coll.height; + abs.radius = coll.radius; + abs.name = coll.name; + abs.axis = (ColliderManager.ColliderAxis)coll.direction; + abs.nativeRef = coll; + abs.colliderTypes |= ColliderManager.ColliderType.UnityEngine; + } + } + + if (magica || magicaBoneHair) + { + if (colliderManager.magicaColliderType == null) + colliderManager.magicaColliderType = GetTypeInAssemblies("MagicaCloth2.MagicaCapsuleCollider"); + + var magicaColl = go.GetComponent(colliderManager.magicaColliderType); + if (magicaColl != null) + { + if (colliderManager.magicaGetSize == null) + colliderManager.magicaGetSize = magicaColl.GetType().GetMethod("GetSize"); + + if (abs.colliderTypes.HasFlag(ColliderManager.ColliderType.UnityEngine)) + { + abs.magicaRef = magicaColl; + } + else + { + abs.transform = go.transform; + abs.isEnabled = go.activeSelf; + abs.localPosition = go.transform.localPosition; + abs.localRotation = go.transform.localRotation; + + // see: https://learn.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.invoke?view=net-7.0 + Vector3 size = (Vector3)colliderManager.magicaGetSize.Invoke(magicaColl, new object[] { }); + abs.height = size.z; + abs.radius = size.x; + + if (GetTypeProperty(magicaColl, "name", out object _name)) + abs.name = (string)_name; + + if (GetTypeField(magicaColl, "direction", out object _axis)) + abs.axis = (ColliderManager.ColliderAxis)_axis; + + abs.magicaRef = magicaColl; + } + abs.colliderTypes |= ColliderManager.ColliderType.MagicaCloth2; + } + } + if (dynamic) + { + if (colliderManager.dynamicBoneColliderType == null) + colliderManager.dynamicBoneColliderType = GetTypeInAssemblies("DynamicBoneCollider"); + + var dynamicColl = go.GetComponent(colliderManager.dynamicBoneColliderType); + if( dynamicColl != null) + { + if (abs.colliderTypes.HasFlag(ColliderManager.ColliderType.UnityEngine) || abs.colliderTypes.HasFlag(ColliderManager.ColliderType.MagicaCloth2)) + { + abs.dynamicRef = dynamicColl; + } + else + { + abs.transform = go.transform; + abs.isEnabled = go.activeSelf; + abs.localPosition = go.transform.localPosition; + abs.localRotation = go.transform.localRotation; + + GetTypeField(dynamicColl, "m_Height", out object _height); + abs.height = (float)_height; + + GetTypeField(dynamicColl, "m_Radius", out object _radius); + abs.radius = (float)_radius; + + if (GetTypeProperty(dynamicColl, "name", out object _name)) + abs.name = (string)_name; + + if (GetTypeField(dynamicColl, "m_Direction", out object _axis)) + abs.axis = (ColliderManager.ColliderAxis)_axis; + + abs.dynamicRef = dynamicColl; + } + abs.colliderTypes |= ColliderManager.ColliderType.DynamicBone; + } + } + if (!ColliderManager.AbstractCapsuleCollider.IsNullOrEmpty(abs)) + { + abstractColliders.Add(abs); + } + } + } + if (abstractColliders.Count > 0) + return true; + else + return false; + } + + public static bool MagicaCloth2IsAvailable() + { + // basic magica cloth v2 test -- very slow + Type magicaClothType = GetTypeInAssemblies("MagicaCloth2.MagicaCloth"); + return magicaClothType != null; + } + + public static bool DynamicBoneIsAvailable() + { + // basic dynamic bone test -- very slow + Type dynamicBoneType = GetTypeInAssemblies("DynamicBone"); + return dynamicBoneType != null; + } + + public Object AddDynamicBoneCollider(GameObject g, CollisionShapeData collider) + { + Type dynamicBoneColliderType = GetTypeInAssemblies("DynamicBoneCollider"); + if (dynamicBoneColliderType != null) + { + var dynamicBoneColliderComponent = g.AddComponent(dynamicBoneColliderType); + + float r = (collider.radius - collider.margin * PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; + float m_Radius = 0f; + float m_Height = 0f; + Vector3 m_Center = Vector3.zero; + int m_Direction = (int)collider.colliderAxis; + + if (collider.colliderType == ColliderType.Sphere) + { + m_Radius = r; + m_Height = 0f; + } + else if (collider.colliderType == ColliderType.Capsule) + { + m_Radius = r; + m_Height = collider.length * modelScale + r * 2f; + } + else + { + float radius; + float height; + switch (collider.colliderAxis) + { + case ColliderAxis.X: + radius = (collider.extent.y + collider.extent.z) / 4f; + height = collider.extent.x; + break; + case ColliderAxis.Z: + radius = (collider.extent.x + collider.extent.y) / 4f; + height = collider.extent.z; + break; + case ColliderAxis.Y: + default: + radius = (collider.extent.x + collider.extent.z) / 4f; + height = collider.extent.y; + break; + } + m_Radius = radius; + m_Height = height; + } + + SetTypeField(dynamicBoneColliderType, dynamicBoneColliderComponent, "m_Radius", m_Radius); + SetTypeField(dynamicBoneColliderType, dynamicBoneColliderComponent, "m_Height", m_Height); + SetTypeField(dynamicBoneColliderType, dynamicBoneColliderComponent, "m_Center", m_Center); + SetTypeField(dynamicBoneColliderType, dynamicBoneColliderComponent, "m_Direction", m_Direction); + + return dynamicBoneColliderComponent; + } + return null; + } + + public Object AddMagicaCloth2Collider(GameObject g, CollisionShapeData collider) + { + Type capsuleColliderType = Physics.GetTypeInAssemblies("MagicaCloth2.MagicaCapsuleCollider"); + if (capsuleColliderType != null) + { + var capsuleColliderComponent = g.AddComponent(capsuleColliderType); + + Physics.SetTypeField(capsuleColliderComponent.GetType(), capsuleColliderComponent, "direction", (int)collider.colliderAxis); + Physics.SetTypeField(capsuleColliderComponent.GetType(), capsuleColliderComponent, "radiusSeparation", false); + + float r; + float h; + if (collider.colliderType.Equals(ColliderType.Capsule)) + { + r = (collider.radius - collider.margin * Physics.PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; + h = collider.length * modelScale + r * 2f; + } + else if (collider.colliderType.Equals(ColliderType.Sphere)) + { + float radius = (collider.radius - collider.margin * PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; + r = radius; + h = 0f; + } + else + { + float radius; + float height; + switch (collider.colliderAxis) + { + case ColliderAxis.X: + radius = (collider.extent.y + collider.extent.z) / 4f; + height = collider.extent.x; + break; + case ColliderAxis.Z: + radius = (collider.extent.x + collider.extent.y) / 4f; + height = collider.extent.z; + break; + case ColliderAxis.Y: + default: + radius = (collider.extent.x + collider.extent.z) / 4f; + height = collider.extent.y; + break; + } + r = (radius - collider.margin * PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; + h = height * modelScale; + } + + //"https://learn.microsoft.com/en-us/dotnet/api/system.type.getmethod?view=netframework-4.8#System_Type_GetMethod_System_String_System_Type___" + MethodInfo setSize = capsuleColliderComponent.GetType().GetMethod("SetSize", + BindingFlags.Public | BindingFlags.Instance, + null, + CallingConventions.Any, + new Type[] { typeof(Vector3) }, + null); + Vector3 sizeVector = new Vector3(r, r, h); + object[] inputParams = new object[] { sizeVector }; + setSize.Invoke(capsuleColliderComponent, inputParams); + + MethodInfo update = capsuleColliderComponent.GetType().GetMethod("UpdateParameters"); + update.Invoke(capsuleColliderComponent, new object[] { }); + + return capsuleColliderComponent; + } + return null; + } + + public Object AddNativeCollider(GameObject g, CollisionShapeData collider) + { + if (collider.colliderType.Equals(ColliderType.Capsule)) + { + CapsuleCollider c = g.AddComponent(); + + c.direction = (int)collider.colliderAxis; + float radius = (collider.radius - collider.margin * PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; + c.radius = radius; + c.height = collider.length * modelScale + radius * 2f; + return c; + } + else if (collider.colliderType.Equals(ColliderType.Sphere)) + { + CapsuleCollider c = g.AddComponent(); + + c.direction = (int)collider.colliderAxis; + float radius = (collider.radius - collider.margin * PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; + c.radius = radius; + c.height = 0f; + return c; + } + else + { + CapsuleCollider c = g.AddComponent(); + c.direction = (int)collider.colliderAxis; + float radius; + float height; + switch (collider.colliderAxis) + { + case ColliderAxis.X: + radius = (collider.extent.y + collider.extent.z) / 4f; + height = collider.extent.x; + break; + case ColliderAxis.Z: + radius = (collider.extent.x + collider.extent.y) / 4f; + height = collider.extent.z; + break; + case ColliderAxis.Y: + default: + radius = (collider.extent.x + collider.extent.z) / 4f; + height = collider.extent.y; + break; + } + c.radius = (radius - collider.margin * PHYSICS_SHRINK_COLLIDER_RADIUS) * modelScale; + c.height = height * modelScale; + return c; + } + } + + private Texture2D ConvertWeightmap(SoftPhysicsData data) + { + Texture2D weightMap = GetTextureFrom(data.weightMapPath, data.materialName, "WeightMap", out string texName, true); + if (!weightMap.isReadable) return null; + + Texture2D lowOutputMap; + bool useCompute = true; + + if (useCompute) + { + CharacterInfo currentCharacter = ImporterWindow.Current.Character; + + string[] folders = new string[] { "Assets", "Packages" }; + Texture2D physXWeightMap = Util.FindTexture(folders, "physXWeightMapTest"); + string folder = ComputeBake.BakeTexturesFolder(currentCharacter.path); + string name = Path.GetFileNameWithoutExtension(data.weightMapPath) + "_" + MAGICA_WEIGHT_SIZE + "_magica";// "magicaWeightMapTest"; + float threshold = 0f; // 1f / 255f; + Vector2Int size = new Vector2Int(MAGICA_WEIGHT_SIZE, MAGICA_WEIGHT_SIZE); + // should create the texture in: /Baked//Textures + lowOutputMap = ComputeBake.BakeMagicaWeightMap(weightMap, threshold, size, folder, name); + //SetWeightMapImport(lowOutputMap); + } + else + { + int sampleX = (int)Mathf.Floor(weightMap.width / 64); + int sampleY = (int)Mathf.Floor(weightMap.height / 64); + + lowOutputMap = new Texture2D(64, 64); + for (int i = 0; i < 64; i++) + { + for (int j = 0; j < 64; j++) + { + Color sample = weightMap.GetPixel(i * sampleX, j * sampleY); + if (sample.g > 0.2f) + { + lowOutputMap.SetPixel(i, j, new Color(0f, 1f, 0f)); + } + else + { + lowOutputMap.SetPixel(i, j, new Color(1f, 0f, 0f)); + } + } + } + lowOutputMap.Apply(); + string assetPath = AssetDatabase.GetAssetPath(weightMap); + string assetDir = Path.GetDirectoryName(assetPath); + string assetExt = Path.GetExtension(assetPath); + string assetName = Path.GetFileNameWithoutExtension(assetPath); + + string outputName = assetName + "_MAGICA"; + string outPutPath = assetDir + "/" + outputName + ".asset"; + AssetDatabase.CreateAsset(lowOutputMap, outPutPath); + AssetDatabase.SaveAssets(); + } + return lowOutputMap; + } + + public IList FetchMagicaColliders(GameObject prefabObject, List matchingBoneList = null) + { + if (!MagicaCloth2IsAvailable()) return null; + + //var magicaColliderType = GetTypeInAssemblies("MagicaCloth2.MagicaCapsuleCollider"); + var magicaColliderType = GetTypeInAssemblies("MagicaCloth2.ColliderComponent"); + IList genericColliders = (IList)CreateGeneric(typeof(List<>), magicaColliderType); + + Transform[] allChildTransforms = prefabObject.GetComponentsInChildren(true); + foreach (Transform childtransform in allChildTransforms) + { + GameObject go = childtransform.gameObject; + var magicaColl = go.GetComponent(magicaColliderType); + if (magicaColl != null) + { + if (matchingBoneList == null) // all magica colliders added to list + { + genericColliders.Add(magicaColl); + } + else if (matchingBoneList.Contains(childtransform.parent.name)) // only magica colliders on the matching bone added to list + { + genericColliders.Add(magicaColl); + } + } + } + return genericColliders; + } + + public IList FetchDynamicBoneColliders(GameObject prefabObject, List matchingBoneList = null) + { + if (!MagicaCloth2IsAvailable()) return null; + + var dynamicBoneColliderType = GetTypeInAssemblies("DynamicBoneColliderBase"); + IList genericColliders = (IList)CreateGeneric(typeof(List<>), dynamicBoneColliderType); + + Transform[] allChildTransforms = prefabObject.GetComponentsInChildren(true); + foreach (Transform childtransform in allChildTransforms) + { + GameObject go = childtransform.gameObject; + var dynBoneColl = go.GetComponent(dynamicBoneColliderType); + if (dynBoneColl != null) + { + if (matchingBoneList == null) // all dynamic bone colliders added to list + { + genericColliders.Add(dynBoneColl); + } + else if (matchingBoneList.Contains(childtransform.parent.name)) // only dynamic bone colliders on the matching bone added to list + { + genericColliders.Add(dynBoneColl); + } + } + } + return genericColliders; + } + + private void ReorderComponentsOfPrefabInstance() + { +#if UNITY_2022_3_OR_NEWER + string currentPrefabAssetPath = AssetDatabase.GetAssetPath(ImporterWindow.Current.Character.PrefabAsset); + GameObject prefabRoot = PrefabUtility.LoadPrefabContents(currentPrefabAssetPath); + //var components = prefabRoot.GetComponents(); + List components = new List(); + prefabRoot.gameObject.GetComponents(components); + int index = 0; + int current = 0; + ColliderManager col = null; + foreach (var component in components) + { + if (component.GetType() == typeof(ColliderManager)) + { + current = index; + col = (ColliderManager)component; + } + index++; + } + for (int i = 0; i < current - 1; i++) + { + UnityEditorInternal.ComponentUtility.MoveComponentUp(col); + } + PrefabUtility.SaveAsPrefabAsset(prefabRoot, currentPrefabAssetPath, out bool success); + PrefabUtility.UnloadPrefabContents(prefabRoot); +#endif + } } } diff --git a/Editor/Pipeline.cs b/Editor/Pipeline.cs index 8897ac8..73e4df0 100644 --- a/Editor/Pipeline.cs +++ b/Editor/Pipeline.cs @@ -20,6 +20,7 @@ using UnityEditor; using UnityEngine; #if HDRP_10_5_0_OR_NEWER +using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; using UnityEditor.Rendering.HighDefinition; #endif @@ -39,7 +40,7 @@ public enum MaterialQuality { None, Default, High, Baked } public static class Pipeline { - public const string VERSION = "1.5.2"; + public const string VERSION = "1.6.0"; #if HDRP_10_5_0_OR_NEWER // version diff --git a/Editor/QuickJSON.cs b/Editor/QuickJSON.cs index 2f3d4bb..fd7a7b5 100644 --- a/Editor/QuickJSON.cs +++ b/Editor/QuickJSON.cs @@ -306,6 +306,8 @@ public QuickJSON GetObjectAtPath(string[] paths) { if (paths.Length > 0) { + if (string.IsNullOrEmpty(paths[0])) + return this; MultiValue mv = GetValue(paths[0]); if (paths.Length > 1 && mv.Type == MultiType.Object) return mv.ObjectValue.GetObjectAtPath(paths.Skip(1).ToArray()); diff --git a/Editor/RL.cs b/Editor/RL.cs index 6188b2c..b31cfd6 100644 --- a/Editor/RL.cs +++ b/Editor/RL.cs @@ -22,6 +22,7 @@ using System; using System.IO; using UnityEditor.Animations; +using System.Reflection; namespace Reallusion.Import { @@ -119,6 +120,16 @@ public static BaseGeneration GetCharacterGeneration(GameObject fbx, string gener return BaseGeneration.Unknown; } + public static void ForceLegacyBlendshapeNormals(ModelImporter importer) + { + string pName = "legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes"; + PropertyInfo prop = importer.GetType().GetProperty(pName, + BindingFlags.Instance | + BindingFlags.NonPublic | + BindingFlags.Public); + prop.SetValue(importer, true); + } + public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer, CharacterInfo info, Avatar avatar = null) { // import normals to avoid mesh smoothing issues @@ -129,12 +140,12 @@ public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer switch(importSet) { case 0: // From CC3/4 - importer.importNormals = ModelImporterNormals.Calculate; + importer.importNormals = ModelImporterNormals.Import; importer.importBlendShapes = true; - importer.importBlendShapeNormals = ModelImporterNormals.Calculate; + importer.importBlendShapeNormals = ModelImporterNormals.Import; importer.normalCalculationMode = ModelImporterNormalCalculationMode.AreaAndAngleWeighted; - importer.normalSmoothingSource = ModelImporterNormalSmoothingSource.FromAngle; - importer.normalSmoothingAngle = 120f; + importer.normalSmoothingSource = ModelImporterNormalSmoothingSource.PreferSmoothingGroups; + importer.normalSmoothingAngle = 60f; break; case 1: // From Blender importer.importNormals = ModelImporterNormals.Import; @@ -142,7 +153,7 @@ public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer importer.importBlendShapeNormals = ModelImporterNormals.Import; importer.normalCalculationMode = ModelImporterNormalCalculationMode.AreaAndAngleWeighted; importer.normalSmoothingSource = ModelImporterNormalSmoothingSource.PreferSmoothingGroups; - importer.normalSmoothingAngle = 120f; + importer.normalSmoothingAngle = 60f; break; } importer.importTangents = ModelImporterTangents.CalculateMikk; @@ -151,6 +162,7 @@ public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel; importer.keepQuads = false; importer.weldVertices = true; + ForceLegacyBlendshapeNormals(importer); importer.autoGenerateAvatarMappingIfUnspecified = true; @@ -705,10 +717,7 @@ public static GameObject CreateInstanceFromModel(CharacterInfo info, GameObject } public static GameObject CreateLODInstanceFromModel(CharacterInfo info, GameObject modelSource) - { - GameObject sceneLODInstance = new GameObject(); - LODGroup lodGroup = sceneLODInstance.AddComponent(); - + { Renderer[] renderers = modelSource.transform.GetComponentsInChildren(true); int lodLevels = 0; foreach (Renderer child in renderers) @@ -720,43 +729,16 @@ public static GameObject CreateLODInstanceFromModel(CharacterInfo info, GameObje } } - if (renderers.Length == lodLevels) - { - LOD[] lods = new LOD[lodLevels]; - GameObject lodPrefabTemp = PrefabUtility.InstantiatePrefab(modelSource) as GameObject; - lodPrefabTemp.transform.SetParent(sceneLODInstance.transform, false); - Renderer[] prefabRenderers = lodPrefabTemp.transform.GetComponentsInChildren(true); + bool originalCharacter = renderers.Length != lodLevels; - for (int i = 0; i < lodLevels; i++) // Does not process LOD0 - { - string LODLevel = "_LOD" + (i + 1); - for (int j = 0; j < prefabRenderers.Length; j++) - { - if (prefabRenderers[j].name.Contains(LODLevel)) - { - Renderer[] rendererLOD = new Renderer[1]; - rendererLOD[0] = prefabRenderers[j]; - lods[i] = new LOD(1.0F / (i + 2), rendererLOD); - } - - if (i == lodLevels - 1) - { - lods[i].screenRelativeTransitionHeight = (0.02f); - } - } - } + lodLevels += 1; + LOD[] lods = new LOD[lodLevels]; + GameObject sceneLODInstance = PrefabUtility.InstantiatePrefab(modelSource) as GameObject; + LODGroup lodGroup = sceneLODInstance.AddComponent(); + Renderer[] prefabRenderers = sceneLODInstance.transform.GetComponentsInChildren(true); - lodGroup.SetLODs(lods); - lodGroup.RecalculateBounds(); - } - else + if (originalCharacter) { - lodLevels++; - LOD[] lods = new LOD[lodLevels]; - GameObject lodPrefabTemp = PrefabUtility.InstantiatePrefab(modelSource) as GameObject; - lodPrefabTemp.transform.SetParent(sceneLODInstance.transform, false); - Renderer[] prefabRenderers = lodPrefabTemp.transform.GetComponentsInChildren(true); - List renderersListLOD0 = new List(); for (int i = 0; i < prefabRenderers.Length; i++) // Process LOD0 { @@ -767,26 +749,29 @@ public static GameObject CreateLODInstanceFromModel(CharacterInfo info, GameObje } Renderer[] renderersLOD0 = renderersListLOD0.ToArray(); lods[0] = new LOD((1.0F / (2)), renderersLOD0); - for (int i = 1; i < lodLevels; i++) + } + + for (int i = 1; i < lodLevels; i++) // Does not process LOD0 + { + string LODLevel = "_LOD" + i; + for (int j = 0; j < prefabRenderers.Length; j++) { - string LODLevel = "_LOD" + i; - for (int j = 0; j < prefabRenderers.Length; j++) + if (prefabRenderers[j].name.EndsWith(LODLevel)) { - if (prefabRenderers[j].name.Contains(LODLevel)) - { - Renderer[] rendererLOD = new Renderer[1]; - rendererLOD[0] = prefabRenderers[j]; - lods[i] = new LOD(1.0F / (i + 2), rendererLOD); - } - if (i == lodLevels - 1) - { - lods[i].screenRelativeTransitionHeight = (0.02f); - } + Renderer[] rendererLOD = new Renderer[1]; + rendererLOD[0] = prefabRenderers[j]; + lods[i] = new LOD(1.0F / (i + 2), rendererLOD); + } + + if (i == lodLevels - 1) + { + lods[i].screenRelativeTransitionHeight = 0.02f; } } - lodGroup.SetLODs(lods); - lodGroup.RecalculateBounds(); - } + } + + lodGroup.SetLODs(lods); + lodGroup.RecalculateBounds(); return sceneLODInstance; } @@ -806,7 +791,7 @@ public static int CountLODs(GameObject fbx) foreach (Renderer r in renderers) { int index = r.name.LastIndexOf("_LOD"); - if (index >= 0 && r.name.Length - index == 5 && char.IsDigit(r.name[r.name.Length - 1])) + if (index >= 0 && r.name.Length == index + 5 && char.IsDigit(r.name[r.name.Length - 1])) { // any mesh with a _LOD suffix is a LOD level string levelString = r.name.Substring(r.name.Length - 1, 1); diff --git a/Editor/Util.cs b/Editor/Util.cs index e0b3c58..2ef75d1 100644 --- a/Editor/Util.cs +++ b/Editor/Util.cs @@ -17,11 +17,14 @@ */ using System.IO; +using System; +using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using Object = UnityEngine.Object; using System.Linq; +using System.Data.Sql; namespace Reallusion.Import { @@ -824,7 +827,7 @@ public static AnimationClip GetFirstAnimationClipFromCharacter(GameObject source // first look for an animation that matches the prefab name found = FindAnimation(f, name, false, true); - // then look for an animation that matches the base name of the character (before any _LodSomething) + // then look for an animation that matches the base name of the character (before any _LodN) if (!found) { int index = name.IndexOf("_Lod", System.StringComparison.InvariantCultureIgnoreCase); @@ -987,17 +990,7 @@ public static void ResetPrefabTransforms(GameObject prefabRoot, GameObject prefa if (prefabObj.transform.localScale != source.transform.localScale) resetSca = true; if (resetPos) prefabObj.transform.localPosition = source.transform.localPosition; if (resetRot) prefabObj.transform.localRotation = source.transform.localRotation; - if (resetSca) prefabObj.transform.localScale = source.transform.localScale; - /* - if (resetPos || resetRot || resetSca) - { - string report = "Resetting " + prefabObj.name + ":"; - if (resetPos) report += " Position"; - if (resetRot) report += " Rotation"; - if (resetSca) report += " Scale"; - Debug.Log(report); - } - */ + if (resetSca) prefabObj.transform.localScale = source.transform.localScale; } for (int i = 0; i < prefabObj.transform.childCount; i++) @@ -1142,7 +1135,11 @@ public static bool NameContainsKeywords(string name, params string[] keyword) return false; } - + private static Editor MakeEditor(string guid) + { + Object o = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid), typeof(Object)); + return Editor.CreateEditor(o); + } const string prefsFailString = "xxxxxxxxxxxxxx"; const char delimiterChar = ','; @@ -1153,10 +1150,10 @@ public static bool TrySerializeAssetToEditorPrefs(Object asset, string editorPre if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(assetInstanceID, out string guid, out long localid)) { string outString = assetInstanceID.ToString() + delimiterChar + guid.ToString() + delimiterChar + localid.ToString(); - LogInfo("Instance ID: " + assetInstanceID.ToString()); - LogInfo("GUID: " + guid.ToString()); - LogInfo("localID: " + localid.ToString()); - LogInfo("outString: " + outString); + LogDetail("Instance ID: " + assetInstanceID.ToString()); + LogDetail("GUID: " + guid.ToString()); + LogDetail("localID: " + localid.ToString()); + LogDetail("outString: " + outString); EditorPrefs.SetString(editorPrefsKey, outString); return true; @@ -1195,8 +1192,8 @@ public static bool TryDeSerializeAssetFromEditorPrefs(out Object asset, strin { string[] split = assetString.Split(new char[] { delimiterChar }); - LogInfo("assetString: " + assetString); - LogInfo("split count: " + split.Length); + LogDetail("assetString: " + assetString); + LogDetail("split count: " + split.Length); if (split.Length == 3) { @@ -1204,22 +1201,22 @@ public static bool TryDeSerializeAssetFromEditorPrefs(out Object asset, strin string guid = split[1]; long localid = long.Parse(split[2]); - LogInfo("Found Instance ID: " + assetInstanceID.ToString()); - LogInfo("Found GUID: " + guid.ToString()); - LogInfo("Found localID: " + localid.ToString()); + LogDetail("Found Instance ID: " + assetInstanceID.ToString()); + LogDetail("Found GUID: " + guid.ToString()); + LogDetail("Found localID: " + localid.ToString()); Object[] potentials = AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GUIDToAssetPath(guid)); - LogInfo(potentials.Length + " Sub objects found for GUID: " + guid); + LogDetail(potentials.Length + " Sub objects found for GUID: " + guid); if (potentials.Length == 0) { Object potentialAsset = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid), typeof(Object)); if (potentialAsset != null) { - LogInfo(potentialAsset.GetType().Name); + LogDetail(potentialAsset.GetType().Name); if (potentialAsset.GetType() == typeof(T)) { - LogInfo("Successfully found single asset: " + potentialAsset.GetType().Name + " Named: " + potentialAsset.name); + LogDetail("Successfully found single asset: " + potentialAsset.GetType().Name + " Named: " + potentialAsset.name); asset = potentialAsset; return true; } @@ -1235,7 +1232,7 @@ public static bool TryDeSerializeAssetFromEditorPrefs(out Object asset, strin { if (potential.GetType() == typeof(T)) { - LogInfo("Successfully found embedded asset: " + potential.GetType().Name + " Named: " + potential.name); + LogDetail("Successfully found embedded asset: " + potential.GetType().Name + " Named: " + potential.name); asset = potential; return true; } @@ -1333,6 +1330,14 @@ public static void LogInfo(string message) } } + public static void LogDetail(string message) + { + if (LOG_LEVEL >= 3) + { + Debug.Log(message); + } + } + public static void LogWarn(string message) { if (LOG_LEVEL >= 1) @@ -1353,5 +1358,26 @@ public static void LogAlways(string message) { Debug.Log(message); } + + + public static void TransferSkinnedMeshes(GameObject fromPrefab, GameObject toPrefab) + { + GameObject fromInstanceRoot = GameObject.Instantiate(fromPrefab); + GameObject toInstanceRoot = GameObject.Instantiate(toPrefab); + Transform[] toTransforms = toInstanceRoot.GetComponentsInChildren(); + SkinnedMeshRenderer[] renderers = fromInstanceRoot.GetComponentsInChildren(); + foreach (SkinnedMeshRenderer smr in renderers) + { + GameObject newMesh = GameObject.Instantiate(smr.gameObject); + newMesh.transform.SetParent(toInstanceRoot.transform, true); + SkinnedMeshRenderer newSMR = newMesh.GetComponent(); + for (int i = 0; i < newSMR.bones.Length; i++) + { + string boneName = smr.bones[i].name; + Transform toBone = System.Array.Find(toTransforms, t => t.name.Equals(boneName)); + if (toBone) newSMR.bones[i] = toBone; + } + } + } } } \ No newline at end of file diff --git a/Editor/WindowManager.cs b/Editor/WindowManager.cs index ff8e33b..29ad68f 100644 --- a/Editor/WindowManager.cs +++ b/Editor/WindowManager.cs @@ -151,17 +151,11 @@ public static void OnPlayModeStateChanged(PlayModeStateChange state) public static void OnBeforeAssemblyReload() { - if (AnimationMode.InAnimationMode()) - { - Util.LogWarn("Disabling Animation Mode on editor assembly reload."); - AnimationMode.StopAnimationMode(); - } - - if (LodSelectionWindow.Current) + if (AnimPlayerGUI.IsPlayerShown()) { - Util.LogInfo("Closing Lod Selection Window on editor assembly reload."); - LodSelectionWindow.Current.Close(); - } + HideAnimationPlayer(true); + HideAnimationRetargeter(true); + } } public static PreviewScene OpenPreviewScene(GameObject prefab) @@ -447,10 +441,12 @@ public static void ShowAnimationPlayer() public static void HideAnimationPlayer(bool updateShowPlayer) { - if (AnimPlayerGUI.IsPlayerShown()) AnimPlayerGUI.ResetFace(); - - AnimPlayerGUI.ClosePlayer(); - + if (AnimPlayerGUI.IsPlayerShown()) + { + AnimPlayerGUI.ResetFace(); + AnimPlayerGUI.ResetCharacterPose(); + AnimPlayerGUI.ClosePlayer(); + } HideAnimationRetargeter(false); if (updateShowPlayer) diff --git a/Icons/RL_Edit_Disable.png b/Icons/RL_Edit_Disable.png new file mode 100644 index 0000000..3ca6fa9 Binary files /dev/null and b/Icons/RL_Edit_Disable.png differ diff --git a/Icons/RL_Edit_Disable.png.meta b/Icons/RL_Edit_Disable.png.meta new file mode 100644 index 0000000..95269e7 --- /dev/null +++ b/Icons/RL_Edit_Disable.png.meta @@ -0,0 +1,104 @@ +fileFormatVersion: 2 +guid: ac8c86276a7ea8c4d9c251de9cf0e878 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RL_Edit_Enable.png b/Icons/RL_Edit_Enable.png new file mode 100644 index 0000000..c9c2d4b Binary files /dev/null and b/Icons/RL_Edit_Enable.png differ diff --git a/Icons/RL_Edit_Enable.png.meta b/Icons/RL_Edit_Enable.png.meta new file mode 100644 index 0000000..5cbc1dd --- /dev/null +++ b/Icons/RL_Edit_Enable.png.meta @@ -0,0 +1,104 @@ +fileFormatVersion: 2 +guid: d45e8bf9ec09a0c4d9d127c5b8425b1e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index d97ad0c..0706e6a 100644 --- a/README.md +++ b/README.md @@ -17,23 +17,25 @@ Links [HDRP Version](https://github.com/soupday/cc_unity_tools_HDRP) Note: There are two verions of the HDRP package -- [**CC Unity Tools HDRP10**](https://github.com/soupday/cc_unity_tools_HDRP/releases/tag/1.5.2.HDRP10) for Unity 2020.3 to 2021.1 -- [**CC Unity Tools HDRP12**](https://github.com/soupday/cc_unity_tools_HDRP/releases/tag/1.5.2.HDRP12) for Unity 2021.2 and upwards +- [**CC Unity Tools HDRP10**](https://github.com/soupday/cc_unity_tools_HDRP/releases/tag/1.6.0.HDRP10) for Unity 2020.3+ +- [**CC Unity Tools HDRP12**](https://github.com/soupday/cc_unity_tools_HDRP/releases/tag/1.6.0.HDRP12) for Unity 2021.2+ The main repository contains the HDRP10 version. See the releases page for the HDRP12 version. [URP Version](https://github.com/soupday/cc_unity_tools_URP) -Note: There are two verions of the URP package -- [**CC Unity Tools URP10**](https://github.com/soupday/cc_unity_tools_URP/releases/tag/1.5.2.URP10) for Unity 2020.3 to 2021.1 -- [**CC Unity Tools URP12**](https://github.com/soupday/cc_unity_tools_URP/releases/tag/1.5.2.URP12) for Unity 2021.2 and upwards +Note: There are four verions of the URP package +- [**CC Unity Tools URP10**](https://github.com/soupday/cc_unity_tools_URP/releases/tag/1.6.0.URP10) for Unity 2020.3+ +- [**CC Unity Tools URP12**](https://github.com/soupday/cc_unity_tools_URP/releases/tag/1.6.0.URP12) for Unity 2021.2+ +- [**CC Unity Tools URP14**](https://github.com/soupday/cc_unity_tools_URP/releases/tag/1.6.0.URP14) for Unity 2022.3+ +- [**CC Unity Tools URP15**](https://github.com/soupday/cc_unity_tools_URP/releases/tag/1.6.0.URP15) for Unity 2023.1+ -The main repository contains the URP10 version. See the releases page for the URP12 version. +The main repository contains the URP10 version. See the releases page for the URP12/14/15 version. [3D/Built-in Version](https://github.com/soupday/cc_unity_tools_3D) The built-in pipeline version is for Unity 2019.4 and upwards. -- [**CC Unity Tools 3D**](https://github.com/soupday/cc_unity_tools_3D/releases/tag/1.5.2) for Unity 2019.4 and upwards +- [**CC Unity Tools 3D**](https://github.com/soupday/cc_unity_tools_3D/releases/tag/1.6.0) for Unity 2019.4+ How it works ============ diff --git a/Runtime/ColliderManager.cs b/Runtime/ColliderManager.cs index c79aaee..c398b2d 100644 --- a/Runtime/ColliderManager.cs +++ b/Runtime/ColliderManager.cs @@ -19,6 +19,9 @@ using System.Collections.Generic; using UnityEngine; using System; +using System.Collections; +using System.Reflection; +using UnityEditor; namespace Reallusion.Import { @@ -26,6 +29,493 @@ namespace Reallusion.Import public class ColliderManager : MonoBehaviour { #if UNITY_EDITOR + // additions to transpose the capsule maipulation code + [HideInInspector][Flags] public enum ColliderType { UnityEngine = 1, MagicaCloth2 = 2, DynamicBone = 4, Unknown = 8 } + [HideInInspector] public ColliderType currentEditType; + [HideInInspector] public ColliderType availableColliderTypes; + [HideInInspector] public enum ManipulatorType { position, rotation, scale } + [HideInInspector] public ManipulatorType manipulator = ManipulatorType.position; + [HideInInspector] public string[] manipulatorArray = { "Position", "Rotation", "Scale" }; + [HideInInspector] public enum ColliderAxis { x, y, z } + [HideInInspector] public bool transformSymmetrically = true; + [HideInInspector] public bool frameSymmetryPair = false; + [HideInInspector] public enum MirrorPlane { x, z } + [HideInInspector] public MirrorPlane selectedMirrorPlane; + [HideInInspector] public IList genericColliderList; + [HideInInspector] public bool magicaCloth2Available; + [HideInInspector] public bool dynamicBoneAvailable; + [HideInInspector] public Type magicaColliderType; + [HideInInspector] public Type dynamicBoneColliderType; + [HideInInspector] public MethodInfo magicaUpdate; + [HideInInspector] public MethodInfo magicaSetSize; + [HideInInspector] public MethodInfo magicaGetSize; + [HideInInspector] public bool hasGizmoUtility = false; + [HideInInspector] public List abstractedCapsuleColliders; + [HideInInspector] public AbstractCapsuleCollider cachedSelectedCollider; + [HideInInspector] public AbstractCapsuleCollider cachedMirrorImageCollider; + [HideInInspector] public AbstractCapsuleCollider selectedAbstractCapsuleCollider; + [HideInInspector] public AbstractCapsuleCollider mirrorImageAbstractCapsuleCollider; + + [Serializable] + public class AbstractCapsuleCollider + { + public Transform transform; + public bool isEnabled; + public Vector3 localPosition; + public Quaternion localRotation; + public float height; + public float radius; + public string name; + public ColliderAxis axis; + public ColliderType colliderTypes; + public UnityEngine.Object nativeRef; + public UnityEngine.Object magicaRef; + public UnityEngine.Object dynamicRef; + + public AbstractCapsuleCollider() + { + + } + + public AbstractCapsuleCollider(Transform _transform, Vector3 _position, Quaternion _rotation, float _height, float _radius, string _name, ColliderAxis _axis, bool _enabled = true, ColliderType _colliderTypes = ColliderType.Unknown, UnityEngine.Object _native = null, UnityEngine.Object _magica = null, UnityEngine.Object _dynamic = null) + { + transform = _transform; + localPosition = _position; + localRotation = _rotation; + height = _height; + radius = _radius; + name = _name; + axis = _axis; + isEnabled = _enabled; + colliderTypes = _colliderTypes; + nativeRef = _native; + magicaRef = _magica; + dynamicRef = _dynamic; + } + + public static bool IsNullOrEmpty(AbstractCapsuleCollider c) + { + bool result = false; + if (c == null) + return true; + + if (c.height == 0f && c.radius == 0f && string.IsNullOrEmpty(c.name)) + return true; + + return result; + } + } + + [Serializable] + public class GizmoState + { + public bool gizmosEnabled; + public bool capsuleEnabled; + public bool clothEnabled; + public bool sphereEnabled; + public bool boxEnabled; + public bool magicaCapsuleEnabled; + public bool magicaCapsuleIconEnabled; + public bool magicaClothEnabled; + public bool magicaClothIconEnabled; + public bool magicaSphereEnabled; + public bool magicaSphereIconEnabled; + public bool magicaPlaneEnabled; + public bool magicaPlaneIconEnabled; + public float iconSize; + public bool iconsEnabled; + + public GizmoState() + { + gizmosEnabled = false; + capsuleEnabled = false; + clothEnabled = false; + sphereEnabled = false; + boxEnabled = false; + magicaCapsuleEnabled = false; + magicaCapsuleIconEnabled = false; + magicaClothEnabled = false; + magicaClothIconEnabled = false; + magicaSphereEnabled = false; + magicaSphereIconEnabled = false; + magicaPlaneEnabled = false; + magicaPlaneIconEnabled = false; + iconSize = 0f; + iconsEnabled = false; + } + } + + [HideInInspector] + public string[] gizmoNames = new string[] + { + "CapsuleCollider", + "Cloth", + "SphereCollider", + "BoxCollider", + "MagicaCapsuleCollider", + "MagicaCloth", + "MagicaSphereCollider", + "MagicaPlaneCollider" + }; + + public void UpdateColliderFromAbstract(Vector3 mirrorPosDiff, Quaternion localRotation) + { + //int selectedIndex = abstractedCapsuleColliders.IndexOf(selectedAbstractCapsuleCollider); + //var genericCollider = genericColliderList[selectedIndex] as UnityEngine.Object; + + Vector3 localEuler = localRotation.eulerAngles; + // transform (as a property of the collider) is inherited and the object ref is already stored + //SetTypeProperty(genericCollider, "transform", selectedAbstractCapsuleCollider.transform); + //SetTypeProperty(genericCollider, "height", selectedAbstractCapsuleCollider.height); + //SetTypeProperty(genericCollider, "radius", selectedAbstractCapsuleCollider.radius); + SyncNativeCollider(selectedAbstractCapsuleCollider); + if (magicaCloth2Available) SyncMagicaCollider(selectedAbstractCapsuleCollider); + if (dynamicBoneAvailable) SyncDynamicBoneCollider(selectedAbstractCapsuleCollider); + + //if (mirrorImageAbstractCapsuleCollider != null) + if (!AbstractCapsuleCollider.IsNullOrEmpty(mirrorImageAbstractCapsuleCollider)) + { + mirrorImageAbstractCapsuleCollider.height = selectedAbstractCapsuleCollider.height; + mirrorImageAbstractCapsuleCollider.radius = selectedAbstractCapsuleCollider.radius; + + //int mirrorIndex = abstractedCapsuleColliders.IndexOf(mirrorImageAbstractCapsuleCollider); + //var mirrorGenericCollider = genericColliderList[mirrorIndex] as UnityEngine.Object; + //SetTypeProperty(mirrorGenericCollider, "height", mirrorImageAbstractCapsuleCollider.height); + //SetTypeProperty(mirrorGenericCollider, "radius", mirrorImageAbstractCapsuleCollider.radius); + SyncNativeCollider(mirrorImageAbstractCapsuleCollider); + if (magicaCloth2Available) SyncMagicaCollider(mirrorImageAbstractCapsuleCollider); + if (dynamicBoneAvailable) SyncDynamicBoneCollider(selectedAbstractCapsuleCollider); + + Transform t = mirrorImageAbstractCapsuleCollider.transform; + Vector3 diff = Vector3.zero; + Quaternion rDiff = Quaternion.identity; + switch (selectedMirrorPlane) + { + case MirrorPlane.x: + { + // rotation + rDiff = Quaternion.Euler(localEuler.x, -localEuler.y, -localEuler.z); + + // position + diff = new Vector3(-mirrorPosDiff.x, mirrorPosDiff.y, mirrorPosDiff.z); + + break; + } + case MirrorPlane.z: + { + // rotation + rDiff = Quaternion.Euler(localEuler.x, -localEuler.y, -localEuler.z); + + // position + diff = new Vector3(-mirrorPosDiff.x, mirrorPosDiff.y, mirrorPosDiff.z); + + break; + } + } + t.localPosition = diff; + t.localRotation = rDiff; + + mirrorImageAbstractCapsuleCollider.transform = t; + } + } + + public void SyncNativeCollider(AbstractCapsuleCollider collider) + { + if (collider.nativeRef != null) + { + CapsuleCollider c = collider.nativeRef as CapsuleCollider; + c.height = collider.height; + c.radius = collider.radius; + } + } + + public void SyncMagicaCollider(AbstractCapsuleCollider collider) + { + if (collider.magicaRef != null) + { + var c = collider.magicaRef; + if (magicaColliderType == null) + magicaColliderType = GetTypeInAssemblies("MagicaCloth2.MagicaCapsuleCollider"); + + if (magicaSetSize == null || magicaUpdate == null) + { + magicaSetSize = c.GetType().GetMethod("SetSize", + BindingFlags.Public | BindingFlags.Instance, + null, + CallingConventions.Any, + new Type[] { typeof(Vector3) }, + null); + + magicaUpdate = c.GetType().GetMethod("UpdateParameters"); + } + Vector3 sizeVector = new Vector3(collider.radius, collider.radius, collider.height); + object[] inputParams = new object[] { sizeVector }; + magicaSetSize.Invoke(c, inputParams); + magicaUpdate.Invoke(c, new object[] { }); + } + } + + public void SyncDynamicBoneCollider(AbstractCapsuleCollider collider) + { + if (collider.dynamicRef != null) + { + var c = collider.dynamicRef; + if (dynamicBoneColliderType == null) + dynamicBoneColliderType = GetTypeInAssemblies("DynamicBoneCollider"); + + SetTypeField(dynamicBoneColliderType, c, "m_Radius", collider.radius); + SetTypeField(dynamicBoneColliderType, c, "m_Height", collider.height); + } + } + + public void CacheCollider(AbstractCapsuleCollider collider, AbstractCapsuleCollider mirrorCollider = null) + { + cachedSelectedCollider = new AbstractCapsuleCollider(null, collider.transform.localPosition, collider.transform.localRotation, collider.height, collider.radius, collider.name, collider.axis); + + if (mirrorCollider != null) + { + cachedMirrorImageCollider = new AbstractCapsuleCollider(null, mirrorCollider.transform.localPosition, mirrorCollider.transform.localRotation, mirrorCollider.height, mirrorCollider.radius, mirrorCollider.name, mirrorCollider.axis); + } + else + { + cachedMirrorImageCollider = null; + } + } + + public AbstractCapsuleCollider DetermineMirrorImageCollider(AbstractCapsuleCollider collider, List colliderList) + { + if (!transformSymmetrically) { return null; } + + if (DetermineMirrorImageColliderName(collider.name, out string mirrorName, out selectedMirrorPlane)) + return colliderList.Find(x => x.name == mirrorName); + else + return null; + } + + public bool DetermineMirrorImageColliderName(string name, out string mirrorName, out MirrorPlane mirrorPlane) + { + // All mirror image determination rules in one place + mirrorName = ""; + mirrorPlane = MirrorPlane.x; + + if (name.Contains("_L_")) + { + mirrorName = name.Replace("_L_", "_R_"); + mirrorPlane = MirrorPlane.x; + } + else if (name.Contains("_R_")) + { + mirrorName = name.Replace("_R_", "_L_"); + mirrorPlane = MirrorPlane.x; + } + else if (name == "CC_Base_NeckTwist01_Capsule(1)") + { + mirrorName = "CC_Base_NeckTwist01_Capsule(2)"; + mirrorPlane = MirrorPlane.z; + } + else if (name == "CC_Base_NeckTwist01_Capsule(2)") + { + mirrorName = "CC_Base_NeckTwist01_Capsule(1)"; + mirrorPlane = MirrorPlane.z; + } + else if (name == "CC_Base_Hip_Capsule") + { + mirrorName = "CC_Base_Hip_Capsule(0)"; + mirrorPlane = MirrorPlane.x; + } + else if (name == "CC_Base_Hip_Capsule(0)") + { + mirrorName = "CC_Base_Hip_Capsule"; + mirrorPlane = MirrorPlane.x; + } + + return !string.IsNullOrEmpty(mirrorName); + } + + public void ResetColliderFromCache() + { + if (selectedAbstractCapsuleCollider != null && cachedSelectedCollider != null) + { + //int index = abstractedCapsuleColliders.IndexOf(selectedAbstractCapsuleCollider); + //if (index != -1) + UpdateColliderSettings(cachedSelectedCollider, selectedAbstractCapsuleCollider);//, index); + } + if (!AbstractCapsuleCollider.IsNullOrEmpty(mirrorImageAbstractCapsuleCollider) && !AbstractCapsuleCollider.IsNullOrEmpty(cachedMirrorImageCollider)) + { + //int mirrorIndex = abstractedCapsuleColliders.IndexOf(mirrorImageAbstractCapsuleCollider); + //if (mirrorIndex != -1) + UpdateColliderSettings(cachedMirrorImageCollider, mirrorImageAbstractCapsuleCollider);//, mirrorIndex); + } + } + + public bool SetTypeProperty(object o, string property, object value) + { + PropertyInfo propertyInfo = o.GetType().GetProperty(property); + if (propertyInfo != null) + { + propertyInfo.SetValue(o, value); + return true; + } + return false; + } + + public bool SetTypeField(Type t, object o, string field, object value) + { + FieldInfo fRoots = t.GetField(field); + if (fRoots != null) + { + fRoots.SetValue(o, value); + return true; + } + return false; + } + + public static bool GetTypeProperty(object o, string property, out object value) + { + PropertyInfo propertyInfo = o.GetType().GetProperty(property); + if (propertyInfo != null) + { + value = propertyInfo.GetValue(o); + return true; + } + value = null; + return false; + } + + public static System.Type GetTypeInAssemblies(string typeName) + { + Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); + foreach (Assembly a in assemblies) + { + System.Type[] types = a.GetTypes(); + foreach (System.Type t in types) + { + if (typeName == t.FullName) + { + return t; + } + } + } + + return null; + } + + /// See: https://stackoverflow.com/questions/10754150/dynamic-type-with-lists-in-c-sharp + public static object CreateGeneric(Type generic, Type innerType, params object[] args) + { + System.Type specificType = generic.MakeGenericType(new System.Type[] { innerType }); + return Activator.CreateInstance(specificType, args); + } + + public void ResetAbstractColliders(List referenceList) + { + if (abstractedCapsuleColliders != null && referenceList != null) + { + foreach (var s in referenceList) + { + AbstractCapsuleCollider current = abstractedCapsuleColliders.Find(x => x.name == s.name); // collider in the real world list corresponding to s + int colliderIndex = abstractedCapsuleColliders.FindIndex(x => x.name == s.name); + + if (current != null && colliderIndex != -1) + UpdateColliderSettings(s, current);//, colliderIndex); + } + } + SyncTransformActiveStatus(); + } + + public void ResetSingleAbstractCollider(List referenceList, string colliderName, bool resetMirror) + { + // reset the specified collider name in abstractedCapsuleColliders with data from referenceList + if (abstractedCapsuleColliders != null && referenceList != null && !string.IsNullOrEmpty(colliderName)) + { + AbstractCapsuleCollider target = abstractedCapsuleColliders.Find(x => x.name == colliderName); + AbstractCapsuleCollider source = referenceList.Find(y => y.name == colliderName); + + int targetIndex = abstractedCapsuleColliders.FindIndex(x => x.name == colliderName); + int sourceIndex = referenceList.FindIndex(y => y.name == colliderName); + + if (targetIndex == sourceIndex) + UpdateColliderSettings(source, target);//, targetIndex); + + if (resetMirror && !AbstractCapsuleCollider.IsNullOrEmpty(mirrorImageAbstractCapsuleCollider)) // determine the mirror in abstractedCapsuleColliders and reset it with data from the corresponding mirror in referenceList + { + AbstractCapsuleCollider mirrorTarget = DetermineMirrorImageCollider(target, abstractedCapsuleColliders); + AbstractCapsuleCollider mirrorSource = DetermineMirrorImageCollider(target, referenceList); + int mirrorTargetIndex = abstractedCapsuleColliders.FindIndex(x => x.name == mirrorTarget.name); + int mirrorSourceIndex = referenceList.FindIndex(y => y.name == mirrorSource.name); + + if (!AbstractCapsuleCollider.IsNullOrEmpty(mirrorTarget) && !AbstractCapsuleCollider.IsNullOrEmpty(mirrorSource)) + { + if (mirrorTargetIndex == mirrorSourceIndex) + UpdateColliderSettings(mirrorSource, mirrorTarget);//, mirrorTargetIndex); + } + } + } + SyncTransformActiveStatus(); + } + + public void UpdateColliderSettings(AbstractCapsuleCollider source, AbstractCapsuleCollider target)//, int genericIndex) + { + // update the real world information with the stored info + target.transform.localPosition = source.localPosition; + target.transform.localRotation = source.localRotation; + + target.height = source.height; + target.radius = source.radius; + target.name = source.name; + target.axis = source.axis; + target.isEnabled = source.isEnabled; + + // native UnityEngine.CapsuleCollider + //var genericCollider = genericColliderList[genericIndex] as UnityEngine.Object; + //SetTypeProperty(genericCollider, "height", source.height); + //SetTypeProperty(genericCollider, "radius", source.radius); + SyncNativeCollider(target); + if (magicaCloth2Available) SyncMagicaCollider(target); + if (dynamicBoneAvailable) SyncDynamicBoneCollider(selectedAbstractCapsuleCollider); + } + + public void SyncTransformActiveStatus() + { + foreach (AbstractCapsuleCollider c in abstractedCapsuleColliders) + { + c.transform.gameObject.SetActive(c.isEnabled); + } + } + + public bool TransformHasAnyValidCollider(Transform t) + { + // return true if any valid collider is present + GameObject go = t.gameObject; + if (go != null) + { + // native + if (go.GetComponent() != null) return true; + + // magica + if (magicaCloth2Available) + { + if (magicaColliderType == null) + magicaColliderType = GetTypeInAssemblies("MagicaCloth2.MagicaCapsuleCollider"); + + if (go.GetComponent(magicaColliderType) != null) return true; + } + + // dynamic bone + if (dynamicBoneAvailable) + { + if (dynamicBoneColliderType == null) + dynamicBoneColliderType = dynamicBoneColliderType = GetTypeInAssemblies("DynamicBoneCollider"); + + if (go.GetComponent(dynamicBoneColliderType) != null) return true; + } + } + return false; + } + + + //end of additions + [Serializable] public class ColliderSettings { @@ -179,14 +669,12 @@ public void Update() } } - public Collider[] colliders; - [HideInInspector] - public GameObject[] clothMeshes; - [HideInInspector] - public ColliderSettings[] settings; - [HideInInspector] - public string characterGUID; + [HideInInspector] public Collider[] colliders; + [HideInInspector] public GameObject[] clothMeshes; + [HideInInspector] public ColliderSettings[] settings; + [HideInInspector] public string characterGUID; + public void AddColliders(List colliders) { List settings = new List(); @@ -199,6 +687,35 @@ public void AddColliders(List colliders) this.colliders = colliders.ToArray(); } + // Accept UnityEngine.Object list as input -- in this case the objects will be transforms + public void AddColliders(List colliders) + { + List settings = new List(); + this.colliders = new Collider[colliders.Count]; + if (colliders != null) + { + //Debug.Log("AddColliders - Adding Object list containing: " + colliders.Count); + if (colliders.Count > 0) + { + for (int i = 0; i < colliders.Count; i++) + { + //Debug.Log("AddColliders - Found Collider Object: " + colliders[i].name + " Type: " + colliders[0].GetType().ToString()); + + if (colliders[i].GetType() == typeof(Transform)) + { + Transform t = (Transform)colliders[i]; + if (t.gameObject.GetComponent() != null) + { + this.colliders[i] = t.gameObject.GetComponent(); + ColliderSettings cs = new ColliderSettings(this.colliders[i]); + settings.Add(cs); + } + } + } + } + } + } + public void UpdateColliders() { foreach (ColliderSettings cs in settings) @@ -241,6 +758,17 @@ public void RefreshData() settings = foundColliderSettings.ToArray(); clothMeshes = foundClothMeshes.ToArray(); } + + // Start or Update message needed to show an enable/disable checkbox in the inspector + private void Start() + { + + } + + private void Update() + { + + } #endif } } \ No newline at end of file diff --git a/Runtime/PhysicsSettingsStore.cs b/Runtime/PhysicsSettingsStore.cs index ea102ba..78776df 100644 --- a/Runtime/PhysicsSettingsStore.cs +++ b/Runtime/PhysicsSettingsStore.cs @@ -28,24 +28,30 @@ namespace Reallusion.Import { - [System.Serializable] - public class PhysicsSettingsStore : ScriptableObject - { + [System.Serializable] + public class PhysicsSettingsStore : ScriptableObject + { #if UNITY_EDITOR - public ColliderSettings[] colliderSettings; - public List clothSettings; - + public ColliderSettings[] colliderSettings; + public List clothSettings; private const string settingsDir = "Settings"; private const string settingsFileName = "PhysicsSettingsStore"; - private const string settingsSuffix = ".asset"; + private const string settingsSuffix = ".asset"; + // additions + public List abstractColliderSettings; + public ColliderManager.GizmoState gizmoState; + //public string[] gizmosToRestore; + //public string[] iconsToRestore; + private const string referenceSettingsDir = "_Reference"; + // end of additions private static string GetSettingsStorePath(Object obj) { string guid = null; if (obj.GetType() == typeof(ColliderManager)) - { + { guid = ((ColliderManager)obj).characterGUID; - } + } else if (obj.GetType() == typeof(WeightMapper)) { guid = ((WeightMapper)obj).characterGUID; @@ -55,7 +61,7 @@ private static string GetSettingsStorePath(Object obj) if (!string.IsNullOrEmpty(guid)) { characterPath = AssetDatabase.GUIDToAssetPath(guid); - } + } else { Debug.LogWarning("Unable to determine character physics store path.\nPlease rebuild physics for this character to correct this."); @@ -97,7 +103,6 @@ public static string GetCharacterPath(Object sceneObject) } } } - return null; } @@ -109,7 +114,7 @@ public static PhysicsSettingsStore SaveColliderSettings(ColliderManager collider workingSettings[i] = new ColliderSettings(colliderManager.settings[i]); } - PhysicsSettingsStore settings = TryFindSettingsObject(colliderManager); + PhysicsSettingsStore settings = TryFindSettingsObject(colliderManager); if (settings) { settings.colliderSettings = workingSettings; @@ -163,13 +168,13 @@ public static PhysicsSettingsStore SaveClothSettings(WeightMapper weightMapper) PhysicsSettings[] workingSettings = new PhysicsSettings[weightMapper.settings.Length]; for (int i = 0; i < weightMapper.settings.Length; i++) { - workingSettings[i] = new PhysicsSettings(weightMapper.settings[i]); + workingSettings[i] = new PhysicsSettings(weightMapper.settings[i]); } PhysicsSettingsStore settings = TryFindSettingsObject(weightMapper); if (settings) - { + { if (settings.clothSettings == null) settings.clothSettings = new List(); foreach (PhysicsSettings s in workingSettings) { @@ -244,7 +249,7 @@ public static bool EnsureAssetsFolderExists(string folder) public static PhysicsSettingsStore TryFindSettingsObject(Object obj) { - string assetPath = GetSettingsStorePath(obj); + string assetPath = GetSettingsStorePath(obj); if (!string.IsNullOrEmpty(assetPath)) { @@ -254,7 +259,7 @@ public static PhysicsSettingsStore TryFindSettingsObject(Object obj) { settingsStore = CreateInstance(); EnsureAssetsFolderExists(Path.GetDirectoryName(assetPath)); - AssetDatabase.CreateAsset(settingsStore, assetPath); + AssetDatabase.CreateAsset(settingsStore, assetPath); } return settingsStore; @@ -279,7 +284,162 @@ private static bool TryGetSavedIndex(List savedClothSettings, P return true; return false; - } + } + + // additions + /* + public static void SaveAbstractColliderSettings(ColliderManager colliderManager) + { + List target = new List(); + List current = colliderManager.abstractedCapsuleColliders; + + foreach (ColliderManager.AbstractCapsuleCollider c in current) + { + target.Add(new ColliderManager.AbstractCapsuleCollider(null, c.transform.position, c.transform.rotation, c.height, c.radius, c.name, c.axis)); + } + + PhysicsSettingsStore settings = TryFindSettingsObject(colliderManager); + if (settings) + { + settings.abstractColliderSettings = target; + EditorUtility.SetDirty(settings); +#if UNITY_2021_2_OR_NEWER + AssetDatabase.SaveAssetIfDirty(AssetDatabase.GUIDFromAssetPath(AssetDatabase.GetAssetPath(settings))); +#else + AssetDatabase.SaveAssets(); +#endif + Debug.Log("Collider settings stored."); + } + } + */ + + public static void SaveAbstractColliderSettings(Object prefab, List abstractColliders = null, bool initialState = false) + { + if (abstractColliders == null) + { + GameObject go = prefab as GameObject; + abstractColliders = go.GetComponent().abstractedCapsuleColliders; + } + List target = new List(); + + foreach (ColliderManager.AbstractCapsuleCollider c in abstractColliders) + { + target.Add(new ColliderManager.AbstractCapsuleCollider(null, c.transform.localPosition, c.transform.localRotation, c.height, c.radius, c.name, c.axis, c.isEnabled)); + } + + PhysicsSettingsStore settings = TryFindSettingsObject(prefab, initialState); + + if (settings) + { + settings.abstractColliderSettings = target; + EditorUtility.SetDirty(settings); +#if UNITY_2021_2_OR_NEWER + AssetDatabase.SaveAssetIfDirty(AssetDatabase.GUIDFromAssetPath(AssetDatabase.GetAssetPath(settings))); +#else + AssetDatabase.SaveAssets(); +#endif + Debug.Log("Collider settings stored."); + } + } + + public static List RecallAbstractColliderSettings(ColliderManager colliderManager, bool initialState = false) + { + PhysicsSettingsStore saved = TryFindSettingsObject(colliderManager, initialState); + + if (saved) + return saved.abstractColliderSettings; + + return null; + } + + public static void SaveGizmoState(ColliderManager colliderManager, ColliderManager.GizmoState gizmoState) + { + PhysicsSettingsStore settings = TryFindSettingsObject(colliderManager); + if (settings) + { + settings.gizmoState = gizmoState; + EditorUtility.SetDirty(settings); +#if UNITY_2021_2_OR_NEWER + AssetDatabase.SaveAssetIfDirty(AssetDatabase.GUIDFromAssetPath(AssetDatabase.GetAssetPath(settings))); +#else + AssetDatabase.SaveAssets(); #endif + //Debug.Log("Gizmo state stored."); + } + } + + public static ColliderManager.GizmoState RecallGizmoState(ColliderManager colliderManager) + { + PhysicsSettingsStore saved = TryFindSettingsObject(colliderManager, false); + + if (saved) + return saved.gizmoState; + + return null; + } + + // original methods extended to accomodate different pathing for the 'initial collider state' save data + // should be ok to replace the original methods and use [bool initialstate = false] as optional parameter + public static PhysicsSettingsStore TryFindSettingsObject(Object obj, bool initialState) + { + string assetPath = GetSettingsStorePath(obj, initialState); + if (!string.IsNullOrEmpty(assetPath)) + { + PhysicsSettingsStore settingsStore = AssetDatabase.LoadAssetAtPath(assetPath); + + if (!settingsStore) + { + settingsStore = CreateInstance(); + EnsureAssetsFolderExists(Path.GetDirectoryName(assetPath)); + AssetDatabase.CreateAsset(settingsStore, assetPath); + } + + return settingsStore; + } + + Debug.LogError("Unable to open physics settings store for character."); + + return null; + } + + private static string GetSettingsStorePath(Object obj, bool initialState) + { + string guid = null; + if (obj.GetType() == typeof(ColliderManager)) + { + guid = ((ColliderManager)obj).characterGUID; + } + else if (obj.GetType() == typeof(WeightMapper)) + { + guid = ((WeightMapper)obj).characterGUID; + } + + string characterPath = null; + if (!string.IsNullOrEmpty(guid)) + { + characterPath = AssetDatabase.GUIDToAssetPath(guid); + } + else + { + Debug.LogWarning("Unable to determine character physics store path.\nPlease rebuild physics for this character to correct this."); + return null; + } + + string characterFolder; + string characterName; + if (!string.IsNullOrEmpty(characterPath)) + { + characterFolder = Path.GetDirectoryName(characterPath); + characterName = Path.GetFileNameWithoutExtension(characterPath); + // determine pathing for the 'initial collider state' save data + if (initialState) + return Path.Combine(characterFolder, settingsDir, characterName + referenceSettingsDir, settingsFileName + settingsSuffix); + else + return Path.Combine(characterFolder, settingsDir, characterName, settingsFileName + settingsSuffix); } + return null; + } + // end of additions +#endif + } } diff --git a/Runtime/WeightMapper.cs b/Runtime/WeightMapper.cs index c5c18aa..77a7465 100644 --- a/Runtime/WeightMapper.cs +++ b/Runtime/WeightMapper.cs @@ -21,6 +21,8 @@ using UnityEngine.Rendering; using UnityEditor; using System; +using Object = UnityEngine.Object; + namespace Reallusion.Import { @@ -216,21 +218,26 @@ public void ApplyWeightMap() { for (int ci = 0; ci < colliders.Count; ci++) { - Collider cc = colliders[ci]; - bool include = false; - if (includeAllLimbColliders) - { - if (cc.name.Contains("_Thigh_")) include = true; - if (cc.name.Contains("_Calf_")) include = true; - if (cc.name.Contains("_Upperarm_")) include = true; - } - if (cc.name.Contains("_Forearm_")) include = true; - if (cc.name.Contains("_Hand_")) include = true; - if (include && !detectedColliders.Contains(cc)) + if (colliders[ci] != null) { - detectedColliders.Add(cc); - colliders.Remove(cc); - ci--; + //Debug.Log("Collider: " + ci + "/" + colliders.Count + " is " + colliders[ci].GetType()); + Collider cc = colliders[ci]; + + bool include = false; + if (includeAllLimbColliders) + { + if (cc.name.Contains("_Thigh_")) include = true; + if (cc.name.Contains("_Calf_")) include = true; + if (cc.name.Contains("_Upperarm_")) include = true; + } + if (cc.name.Contains("_Forearm_")) include = true; + if (cc.name.Contains("_Hand_")) include = true; + if (include && !detectedColliders.Contains(cc)) + { + detectedColliders.Add(cc); + colliders.Remove(cc); + ci--; + } } } } @@ -287,18 +294,20 @@ public void ApplyWeightMap() for (int ci = 0; ci < colliders.Count; ci++) { Collider cc = colliders[ci]; - - if (cc.bounds.Contains(world)) - { - detectedColliders.Add(cc); - colliders.Remove(cc); - ci--; - } - else if (cc.bounds.SqrDistance(world) < worldMax * worldMax) + if (cc != null) { - detectedColliders.Add(cc); - colliders.Remove(cc); - ci--; + if (cc.bounds.Contains(world)) + { + detectedColliders.Add(cc); + colliders.Remove(cc); + ci--; + } + else if (cc.bounds.SqrDistance(world) < worldMax * worldMax) + { + detectedColliders.Add(cc); + colliders.Remove(cc); + ci--; + } } } } diff --git a/Runtime/WrinkleManager.cs b/Runtime/WrinkleManager.cs index ff03d42..ee78be3 100644 --- a/Runtime/WrinkleManager.cs +++ b/Runtime/WrinkleManager.cs @@ -637,11 +637,28 @@ void CheckInit() BuildConfig(); } } - + void Start() { updateTimer = 0f; - CheckInit(); + + if (Application.isPlaying) + { + SkinnedMeshRenderer smr = GetComponent(); + if (smr) + { + foreach (Material mat in smr.materials) + { + if (mat.IsKeywordEnabled("BOOLEAN_IS_HEAD_ON")) + { + headMaterial = mat; + break; + } + } + } + } + + CheckInit(); } void Update() diff --git a/package.json b/package.json index 7f667c4..7ca17bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.soupday.cc3_unity_tools", - "version": "1.5.2", + "version": "1.6.0", "displayName": "CC/iC Unity Tools 3D", "description": "Unity importer for Character Creator 3 & 4 and iClone 7 and 8.", "unity": "2019.4",