diff --git a/CHANGELOG.md b/CHANGELOG.md index d153bfa..c750cb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ Changelog ========= +### v 1.5.0 +- Animation player system rebuilt to remove AnimationMode. + - New system includes foot IK preview, variable speed and camera bone targeting and play mode operation. +- Optional (WIP) HDRP dual specular shader added for HQ skin. (Enabled in the import window options) +- HDRP mask detail mask correction. (Export from CC4 not using correct micro normal mask) +- HDRP lighting fix when first loading preview scene. +- Batch processing window added. +- Physics sphere collider support added. +- Import procedure now processes all _Motion.Fbx files with the character. +- Fix to character build crash in Unity 2023.1 +- Fix to Diffusion profiles not saving to default volume asset in 2023.1. + +### v 1.4.9 +- Refresh button fix... + ### v 1.4.8 - Fix to missing materials in shared material slots. - Fix to two pass hair extraction when re-using the same material. diff --git a/Editor/AnimPlayerGUI.cs b/Editor/AnimPlayerGUI.cs index 21a90aa..0dd8581 100644 --- a/Editor/AnimPlayerGUI.cs +++ b/Editor/AnimPlayerGUI.cs @@ -20,6 +20,8 @@ using UnityEditor; using System.Collections.Generic; using Object = UnityEngine.Object; +using UnityEditor.Animations; +using System; namespace Reallusion.Import { @@ -27,8 +29,8 @@ public static class AnimPlayerGUI { #region AnimPlayer - private static bool play = false; - private static float time, prev, current = 0f; + //private static bool play = false; + //private static float time, prev, current = 0f; public static bool AnimFoldOut { get; private set; } = true; public static FacialProfile MeshFacialProfile { get; private set; } public static FacialProfile ClipFacialProfile { get; private set; } @@ -36,9 +38,10 @@ public static class AnimPlayerGUI public static AnimationClip WorkingClip { get ; set; } public static Animator CharacterAnimator { get; set; } - private static double updateTime = 0f; - private static double deltaTime = 0f; - private static double frameTime = 1f; + //private static double updateTime = 0f; + //private static double deltaTime = 0f; + //private static double frameTime = 1f; + private static bool forceUpdate = false; private static FacialProfile defaultProfile = new FacialProfile(ExpressionProfile.ExPlus, VisemeProfile.PairsCC3); @@ -46,7 +49,7 @@ public static void OpenPlayer(GameObject scenePrefab) { if (scenePrefab) { - scenePrefab = Util.TryResetScenePrefab(scenePrefab); + scenePrefab = Util.TryResetScenePrefab(scenePrefab); SetCharacter(scenePrefab); } @@ -64,7 +67,10 @@ public static void OpenPlayer(GameObject scenePrefab) //Common SceneView.RepaintAll(); - EditorApplication.update += UpdateDelegate; + EditorApplication.update -= UpdateCallback; + EditorApplication.update += UpdateCallback; + EditorApplication.playModeStateChanged -= PlayStateChangeCallback; + EditorApplication.playModeStateChanged += PlayStateChangeCallback; } } @@ -72,7 +78,11 @@ public static void ClosePlayer() { if (IsPlayerShown()) { - EditorApplication.update -= UpdateDelegate; + //clean up controller here + ResetToBaseAnimatorController(); + + EditorApplication.update -= UpdateCallback; + EditorApplication.playModeStateChanged -= PlayStateChangeCallback; WindowManager.StopAnimationMode(); @@ -112,6 +122,11 @@ public static bool IsPlayerShown() public static void SetCharacter(GameObject scenePrefab) { + if (scenePrefab) + Debug.Log("scenePrefab.name: " + scenePrefab.name + " " + PrefabUtility.IsPartOfPrefabInstance(scenePrefab)); + + + if (!scenePrefab && WindowManager.IsPreviewScene) scenePrefab = WindowManager.GetPreviewScene().GetPreviewCharacter(); @@ -119,11 +134,29 @@ public static void SetCharacter(GameObject scenePrefab) { Animator animator = scenePrefab.GetComponent(); if (!animator) animator = scenePrefab.GetComponentInChildren(); - GameObject sceneFbx = Util.FindRootPrefabAssetFromSceneObject(scenePrefab); - AnimationClip clip = Util.GetFirstAnimationClipFromCharacter(sceneFbx); - if (sceneFbx && clip) - clip = AnimRetargetGUI.TryGetRetargetedAnimationClip(sceneFbx, clip); - UpdateAnimatorClip(animator, clip); + if (animator != null) + { + if (PrefabUtility.IsPartOfPrefabInstance(scenePrefab)) + { + GameObject sceneFbx = Util.FindRootPrefabAssetFromSceneObject(scenePrefab); + // in edit mode - find the first animation clip + AnimationClip clip = Util.GetFirstAnimationClipFromCharacter(sceneFbx); + if (sceneFbx && clip) + clip = AnimRetargetGUI.TryGetRetargetedAnimationClip(sceneFbx, clip); + UpdateAnimatorClip(animator, clip); + } + else + { + if (EditorApplication.isPlaying) + { + // in play mode - try to recover the stored last played animation + if (Util.TryDeSerializeAssetFromEditorPrefs(out Object obj, WindowManager.clipKey)) + { + UpdateAnimatorClip(animator, obj as AnimationClip); + } + } + } + } } } @@ -136,6 +169,41 @@ static public void UpdateAnimatorClip(Animator animator, AnimationClip clip) if (!animator || CharacterAnimator != animator) doneInitFace = false; + CharacterAnimator = animator; + OriginalClip = clip; + + SetupCharacterAndAnimation(); + //WorkingClip = CloneClip(OriginalClip); + + AnimRetargetGUI.RebuildClip(); + + MeshFacialProfile = FacialProfileMapper.GetMeshFacialProfile(animator ? animator.gameObject : null); + ClipFacialProfile = FacialProfileMapper.GetAnimationClipFacialProfile(clip); + + time = 0f; + play = false; + + // intitialise the face refs if needed + if (!doneInitFace) InitFace(); + + // finally, apply the face + ApplyFace(); + + if (WorkingClip && CharacterAnimator) + { + // also restarts animation mode + //SampleOnce(); + } + + /* Original Code: + + if (doneInitFace) ResetFace(true, true); + + // stop animation mode + WindowManager.StopAnimationMode(); + + if (!animator || CharacterAnimator != animator) doneInitFace = false; + CharacterAnimator = animator; OriginalClip = clip; WorkingClip = CloneClip(OriginalClip); @@ -158,9 +226,278 @@ static public void UpdateAnimatorClip(Animator animator, AnimationClip clip) { // also restarts animation mode SampleOnce(); - } + } + + */ + } + + #region Animator Setup + // ---------------------------------------------------------------------------- + public static bool showMessages = false; + public static bool sceneFocus = false; + + // initial selection + [SerializeField] + private static AnimatorController originalAnimatorController; + + // Animaton Controller + [SerializeField] + private static AnimatorController playbackAnimatorController; + private static string controllerName = "--Temp-CCiC-Animator-Controller"; + private static string overrideName = "--Temp-CCiC-Override-Controller"; + private static string dirString = "Assets/"; + private static string controllerPath; + private static string defaultState = "default_state"; + [SerializeField] + private static int defaultStateNameHash; + private static string paramDirection = "param_direction"; + [SerializeField] + private static AnimatorState playingState; + [SerializeField] + public static int controlStateHash { get; set; } + + // animator/animation settings + public static bool FootIK = true; + [Flags] + enum AnimatorFlags + { + None = 0, + AnimateOnTheSpot = 1, + ShowMirrorImage = 2, + AutoLoopPlayback = 4, + Everything = ~0 + } + + static AnimatorFlags flagSettings; + + // Animaton Override Controller + [SerializeField] + private static AnimatorOverrideController animatorOverrideController; + + + // Playback + private static bool play = false; + private static float playbackSpeed = 1f; + + [SerializeField] + public static float time = 0f; + private static string realTime = ""; + + // Update + private static double updateTime = 0f; + private static double deltaTime = 0f; + private static double frameTime = 1f; + private static double current = 0f; + + [SerializeField] + private static bool wasPlaying; + + // GUIStyles + private static Styles guiStyles; + + [SerializeField] + private static List boneItemList; + [SerializeField] + public static bool isTracking = false; + [SerializeField] + private static GameObject lastTracked; + private static string boneNotFound = "not found"; + + // ---------------------------------------------------------------------------- + + public static void SetupCharacterAndAnimation() + { + if (CharacterAnimator == null) return; // don't try and set up an override controller if the char has no animator + + // retain the original AnimatorController from the scene model + //originalAnimatorController = GetControllerFromAnimator(CharacterAnimator); + + // construct and use a new controller with specific parameters + playbackAnimatorController = CreateAnimatiorController(); + CharacterAnimator.runtimeAnimatorController = playbackAnimatorController; + + // create an animation override controller from the new controller + animatorOverrideController = CreateAnimatorOverrideController(); + CharacterAnimator.runtimeAnimatorController = animatorOverrideController; + + // defaults contain some flags that must be set in the selected animation + ApplyDefaultSettings(); + + // select the original clip using the override controller + // this method is normally used to switch animations during play mode + SelectOverrideAnimation(OriginalClip, animatorOverrideController); + + // reset the animation player + ResetAnimationPlayer(); + + + + } + + private static AnimatorController CreateAnimatiorController() + { + controllerPath = dirString + controllerName + ".controller"; + + if (showMessages) Debug.Log("Creating Temporary file " + controllerPath); + AnimatorController a = AnimatorController.CreateAnimatorControllerAtPath(controllerPath); + a.name = controllerName; + // play mode parameters + a.AddParameter(paramDirection, AnimatorControllerParameterType.Float); + AnimatorStateMachine rootStateMachine = a.layers[0].stateMachine; + AnimatorState baseState = rootStateMachine.AddState(defaultState); + baseState.iKOnFeet = FootIK; + // play mode parameters + baseState.speedParameter = paramDirection; + baseState.speedParameterActive = true; + baseState.motion = OriginalClip; + playingState = baseState; + controlStateHash = baseState.nameHash; + return a; + } + + private static AnimatorOverrideController CreateAnimatorOverrideController() + { + var aoc = new AnimatorOverrideController(CharacterAnimator.runtimeAnimatorController); + aoc.name = overrideName; + return aoc; + } + + private static void ApplyDefaultSettings() + { + flagSettings = AnimatorFlags.AutoLoopPlayback; + FootIK = true; + CharacterAnimator.SetFloat(paramDirection, 0f); + if (CharacterAnimator != null) + { + CharacterAnimator.applyRootMotion = true; + } + } + + private static void SelectOverrideAnimation(AnimationClip clip, AnimatorOverrideController aoc) + { + ResetAnimationPlayer(); + var clone = GameObject.Instantiate(clip); + clone.name = clip.name; + SetClipSettings(clone); // update the bake flags in AnimationClipSettings + // origingal clipsettings are untouched and should be copied + // directly into any saved animations from the retargeter + WorkingClip = clone; + + List> overrides = new List>(aoc.overridesCount); + aoc.GetOverrides(overrides); + + foreach (var v in overrides) + { + if (showMessages) Debug.Log("Overrides: " + " Key: " + v.Key + " Value: " + v.Value); + } + + overrides[0] = new KeyValuePair(overrides[0].Key, WorkingClip); + aoc.ApplyOverrides(overrides); + FirstFrameButton(); + } + + private static void ResetAnimationPlayer() + { + play = false; + time = 0f; + playbackSpeed = 1f; + + if (EditorApplication.isPlaying) + { + CharacterAnimator.SetFloat(paramDirection, 0f); + CharacterAnimator.Play(controlStateHash, 0, time); + } + else + { + CharacterAnimator.Update(time); + } + CharacterAnimator.gameObject.transform.localPosition = Vector3.zero; + CharacterAnimator.gameObject.transform.rotation = Quaternion.identity; + } + + private static void SetClipSettings(AnimationClip clip) + { + AnimationClipSettings clipSettings = AnimationUtility.GetAnimationClipSettings(clip); + clipSettings.mirror = flagSettings.HasFlag(AnimatorFlags.ShowMirrorImage); + clipSettings.loopBlendPositionXZ = !flagSettings.HasFlag(AnimatorFlags.AnimateOnTheSpot); + clipSettings.loopBlendPositionY = !flagSettings.HasFlag(AnimatorFlags.AnimateOnTheSpot); + clipSettings.loopBlendOrientation = !flagSettings.HasFlag(AnimatorFlags.AnimateOnTheSpot); + AnimationUtility.SetAnimationClipSettings(clip, clipSettings); + CharacterAnimator.applyRootMotion = !flagSettings.HasFlag(AnimatorFlags.AnimateOnTheSpot); + } + + + public static void ResetToBaseAnimatorController() + { + // look up the original prefab corresponding to the model in the preview scene + // extract the path reference to the animator controller being used by the original prefab + // check the scene model is using the override controller created above + // replace the runtime animator controller of the scene model with the animatorcontroller asset at path + // destroy the disk asset temp override controller (that was created above) + + GameObject characterPrefab = WindowManager.GetPreviewScene().GetPreviewCharacter(); + Debug.Log(("Attempting to reset: " + characterPrefab.name)); + + GameObject basePrefab = PrefabUtility.GetCorrespondingObjectFromSource(characterPrefab); + + if (basePrefab != null) + { + if (PrefabUtility.IsAnyPrefabInstanceRoot(basePrefab)) + { + string prefabPath = AssetDatabase.GetAssetPath(basePrefab); + Debug.Log((basePrefab.name + "Prefab instance root found: " + prefabPath)); + + Debug.Log("Loaded Prefab: " + basePrefab.name); + Animator baseAnimator = basePrefab.GetComponent(); + if (baseAnimator != null) + { + Debug.Log("Prefab Animator: " + baseAnimator.name); + if (baseAnimator.runtimeAnimatorController) + { + Debug.Log("Prefab Animator Controller: " + baseAnimator.runtimeAnimatorController.name); + string controllerpath = AssetDatabase.GetAssetPath(baseAnimator.runtimeAnimatorController); + Debug.Log("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) + Debug.Log("Current controller on character: " + CharacterAnimator.runtimeAnimatorController.name); + if (CharacterAnimator.runtimeAnimatorController.GetType() == typeof(AnimatorOverrideController) && CharacterAnimator.runtimeAnimatorController.name == overrideName) + { + Debug.Log("Created override controller found: can reset"); + CharacterAnimator.runtimeAnimatorController = baseController; + } + } + } + else + { + Debug.Log("NO Prefab Animator Controller"); + } + } + } + } + DestroyAnimationController(); } + private static void DestroyAnimationController() + { + Object tempControllerAsset = AssetDatabase.LoadAssetAtPath(controllerPath); + if (tempControllerAsset != null) + { + if (tempControllerAsset.GetType() == typeof(AnimatorController)) + { + //if (showMessages) + + Debug.Log("Override controller: " + controllerPath + " exists -- removing"); + AssetDatabase.DeleteAsset(controllerPath); + } + } + } + + #endregion Animator Setup + public static void ReCloneClip() { WorkingClip = CloneClip(OriginalClip); @@ -181,9 +518,11 @@ public static AnimationClip CloneClip(AnimationClip clip) } return null; - } + } - public static void DrawPlayer() + #region IMGUI + /* //Original DrawPlayer() method + public static void ORIGINALDrawPlayer() { GUILayout.BeginVertical(); EditorGUI.BeginChangeCheck(); @@ -278,18 +617,901 @@ public static void DrawPlayer() } GUILayout.EndVertical(); } + // + */ - public static void SampleOnce() + public class Styles { - if (CharacterAnimator && WorkingClip) + public GUIStyle settingsButton; + public GUIStyle playbackLabelStyle; + public GUIStyle playIconStyle; + public GUIStyle trackIconStyle; + + public Styles() { - if (!AnimationMode.InAnimationMode()) AnimationMode.StartAnimationMode(); - AnimationMode.BeginSampling(); - AnimationMode.SampleAnimationClip(CharacterAnimator.gameObject, WorkingClip, time); - AnimationMode.EndSampling(); + settingsButton = new GUIStyle("toolbarbutton"); + playbackLabelStyle = new GUIStyle("label"); + playbackLabelStyle.alignment = TextAnchor.MiddleRight; + + playIconStyle = new GUIStyle("label"); + playIconStyle.contentOffset = new Vector2(5f, -4f); + + trackIconStyle = new GUIStyle("label"); + trackIconStyle.contentOffset = new Vector2(-6f, -4f); + } } - + + public static void DrawPlayer() + { + if (guiStyles == null) + guiStyles = new Styles(); + + GUILayout.BeginVertical(); + EditorGUI.BeginChangeCheck(); + AnimFoldOut = EditorGUILayout.Foldout(AnimFoldOut, "Animation Playback", EditorStyles.foldout); + if (EditorGUI.EndChangeCheck()) + { + //if (foldOut && FacialMorphIMGUI.FoldOut) + // FacialMorphIMGUI.FoldOut = false; + doOnceCatchMouse = true; + } + if (AnimFoldOut) + { + EditorGUI.BeginChangeCheck(); + Animator selectedAnimator = (Animator)EditorGUILayout.ObjectField(new GUIContent("Scene Model", "Animated model in scene"), CharacterAnimator, typeof(Animator), true); + AnimationClip selectedClip = (AnimationClip)EditorGUILayout.ObjectField(new GUIContent("Animation", "Animation to play and manipulate"), OriginalClip, typeof(AnimationClip), false); + if (EditorGUI.EndChangeCheck()) + { + UpdateAnimatorClip(selectedAnimator, selectedClip); + } + + GUI.enabled = WorkingClip && CharacterAnimator; + + + + + // BEGIN PREFS AREA + + EditorGUILayout.Space(); + var rect = EditorGUILayout.BeginHorizontal(); + Handles.color = Color.gray * 0.2f; + Handles.DrawLine(new Vector2(rect.x + 15, rect.y), new Vector2(rect.width - 15, rect.y)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + + GUILayout.BeginHorizontal(); + + GUILayout.Space(4f); + + EditorGUI.BeginChangeCheck(); + FootIK = GUILayout.Toggle(FootIK, new GUIContent("IK", "Toggle feet IK"), guiStyles.settingsButton, GUILayout.Width(24f), GUILayout.Height(24f)); + if (EditorGUI.EndChangeCheck()) + { + ToggleIKButton(); + } + + // Camera Bone Tracker + + if (!CheckTackingStatus()) + CancelBoneTracking(false); //object focus lost - arrange ui to reflect that, but dont fight with the scene camera + + 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(); + } + Texture cancelTrack = isTracking ? EditorGUIUtility.IconContent("toolbarsearchCancelButtonActive").image : EditorGUIUtility.IconContent("toolbarsearchCancelButtonOff").image; + string cancelTrackTxt = isTracking ? "Cancel camera tracking" : "Camera tracking controls."; + + EditorGUI.BeginDisabledGroup(!isTracking); + if (GUILayout.Button(new GUIContent(cancelTrack, cancelTrackTxt), guiStyles.trackIconStyle, GUILayout.Width(24f), GUILayout.Height(24f))) + { + CancelBoneTracking(true); //tracking deliberately cancelled - leave scene camera in last position with last tracked object still selected + } + EditorGUI.EndDisabledGroup(); + GUILayout.FlexibleSpace(); + + EditorGUI.BeginChangeCheck(); + playbackSpeed = GUILayout.HorizontalSlider(playbackSpeed, -1f, 2f, GUILayout.Width(130f)); + if (Math.Abs(1 - playbackSpeed) < 0.1f) + playbackSpeed = 1f; + if (EditorGUI.EndChangeCheck()) + { + PlaybackSpeedSlider(); + } + GUILayout.Label(new GUIContent(PlaybackText(), "Playback Speed"), guiStyles.playbackLabelStyle, GUILayout.MaxWidth(43f)); + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("Refresh").image, "Reset model to T-Pose and player to defaults"), guiStyles.settingsButton, GUILayout.Width(24f), GUILayout.Height(24f))) + { + ResetCharacterAndPlayer(); + } + + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("d__Menu").image, "Animation Clip Preferences"), guiStyles.settingsButton, GUILayout.Width(20f), GUILayout.Height(20f))) + { + ShowPrefsGenericMenu(); + } + + + GUILayout.Space(4f); + + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + + GUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + var rect2 = EditorGUILayout.BeginHorizontal(); + Handles.color = Color.gray * 0.2f; //new Color(0.1372f, 0.1372f, 0.1372f, 1.0f); + Handles.DrawLine(new Vector2(rect2.x + 15f, rect2.y), new Vector2(rect2.width - 15f, rect2.y)); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + // END PREFS AREA + + GUILayout.BeginHorizontal(); + if (CharacterAnimator && WorkingClip) + { + EditorGUI.BeginChangeCheck(); + time = GUILayout.HorizontalSlider(time, 0f, 1f, GUILayout.Height(24f)); + if (EditorGUI.EndChangeCheck()) + { + ScrubTimeline(); + } + + realTime = TimeText(); + EditorGUI.BeginChangeCheck(); + realTime = EditorGUILayout.DelayedTextField(new GUIContent("", "Time index. Accepts numerical input."), realTime, GUILayout.MaxWidth(42f)); + if (EditorGUI.EndChangeCheck()) + { + ParseTimeInput(); + } + } + else + { + EditorGUI.BeginDisabledGroup(true); + GUILayout.HorizontalSlider(0f, 0f, 1f, GUILayout.Height(24f)); + EditorGUILayout.DelayedTextField(new GUIContent("", "Time index. Accepts numerical input."), "", GUILayout.MaxWidth(42f)); + EditorGUI.EndDisabledGroup(); + } + + EditorGUILayout.EndHorizontal(); + + EditorGUI.BeginDisabledGroup(!(CharacterAnimator && WorkingClip)); + GUILayout.BeginHorizontal(EditorStyles.toolbar); + // "Animation.FirstKey" + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("Animation.FirstKey").image, "First Frame"), EditorStyles.toolbarButton)) + { + FirstFrameButton(); + } + // "Animation.PrevKey" + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("Animation.PrevKey").image, "Previous Frame"), EditorStyles.toolbarButton)) + { + PrevFrameButton(); + } + // "Animation.Play" + Texture playButton = playbackSpeed > 0 ? EditorGUIUtility.IconContent("d_forward@2x").image : EditorGUIUtility.IconContent("d_back@2x").image; + Texture pauseButton = EditorGUIUtility.IconContent("PauseButton").image; + /* + if (GUILayout.Button(new GUIContent(playButton, "Play"), EditorStyles.toolbarButton, GUILayout.Height(30f))) + { + PlayButton(); + } + // "PauseButton" + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("PauseButton").image, "Pause"), EditorStyles.toolbarButton)) + { + PauseButton(); + } + */ + // play/pause: "Animation.Play" / "PauseButton" + + if (GUILayout.Button(new GUIContent(play ? pauseButton : playButton, play ? "Pause" : "Play"), EditorStyles.toolbarButton, GUILayout.Height(30f))) + { + PlayPauseButton(); + } + + // "Animation.NextKey" + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("Animation.NextKey").image, "Next Frame"), EditorStyles.toolbarButton)) + { + NextFrameButton(); + } + // "Animation.LastKey" + if (GUILayout.Button(new GUIContent(EditorGUIUtility.IconContent("Animation.LastKey").image, "Last Frame"), EditorStyles.toolbarButton)) + { + LastFrameButton(); + } + 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)); + + 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."; + if (GUILayout.Button(new GUIContent(bigPlayButton, playToggleTxt), EditorStyles.toolbarButton, GUILayout.Height(24f), GUILayout.Width(60f))) + { + ApplicationPlayToggle(); + } + + + GUILayout.EndHorizontal(); + + /* + EditorGUILayout.Space(28f); + GUILayout.BeginHorizontal(EditorStyles.toolbar); + GUILayout.FlexibleSpace(); + + 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."; + if (GUILayout.Button(new GUIContent(bigPlayButton, playToggleTxt), EditorStyles.toolbarButton, GUILayout.Height(100f), GUILayout.Width(100f))) + { + ApplicationPlayToggle(); + } + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + */ + } + GUILayout.EndVertical(); + } + + #endregion IMGUI + #region Button Events/Functions + // Button functions + + // "Toggle feet IK" + private static void ToggleIKButton() + { + // Alternative method - retrieve a copy of the layers - modify then reapply + // find the controlstate by nameHash + AnimatorControllerLayer[] allLayer = playbackAnimatorController.layers; + for (int i = 0; i < allLayer.Length; i++) + { + ChildAnimatorState[] states = allLayer[i].stateMachine.states; + for (int j = 0; j < states.Length; j++) + { + if (states[j].state.nameHash == controlStateHash) + { + states[j].state.iKOnFeet = FootIK; + allLayer[i].iKPass = FootIK; + } + } + } + if (EditorApplication.isPlaying) CharacterAnimator.gameObject.SetActive(false); + playbackAnimatorController.layers = allLayer; + if (EditorApplication.isPlaying) CharacterAnimator.gameObject.SetActive(true); + + // originally using the cached default state directly ... + // both methods encounter errors when changing foot ik during runtime + // unless the gameObject is disabled/re-enabled + /* + if (EditorApplication.isPlaying) sceneAnimator.gameObject.SetActive(false); + playingState.iKOnFeet = FootIK; + if (EditorApplication.isPlaying) sceneAnimator.gameObject.SetActive(true); + */ + if (EditorApplication.isPlaying) + { + //ResetAnimationPlayer(); + CharacterAnimator.Play(controlStateHash, 0, time); + CharacterAnimator.SetFloat(paramDirection, play ? playbackSpeed : 0f); + } + else + { + CharacterAnimator.Update(time); + } + } + + // playback speed slider sets speed multiplier directly in edit mode but requires an update in play mode + private static void PlaybackSpeedSlider() + { + if (EditorApplication.isPlaying) + { + if (play) + CharacterAnimator.SetFloat(paramDirection, playbackSpeed); + else + CharacterAnimator.SetFloat(paramDirection, 0f); + + CharacterAnimator.Play(controlStateHash, 0, time); + } + } + + // "Reset Model to T-Pose and player to defaults" + public static void ResetCharacterAndPlayer() + { + // re-apply all defaults + ApplyDefaultSettings(); + + // reset the player + ResetAnimationPlayer(); + + // clear all the animation data ============================================================== + // ============================================================================================ + WorkingClip = null; + OriginalClip = null; + // ENSURE RESET OF SELECTION FIELDS TOO + // ============================================================================================ + + // clear the animation controller + override controller + // remove the on-disk temporary controller + ResetToBaseAnimatorController(); + + //DestroyAnimationController(); + // revert character pose to original T-Pose + ResetCharacterPose(); + // user can now select a new animation for playing + } + + // "Animation Clip Preferences" + private static void ShowPrefsGenericMenu() + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Animate On The Spot"), flagSettings.HasFlag(AnimatorFlags.AnimateOnTheSpot), OnPrefSelected, AnimatorFlags.AnimateOnTheSpot); + menu.AddItem(new GUIContent("Show Mirror Image"), flagSettings.HasFlag(AnimatorFlags.ShowMirrorImage), OnPrefSelected, AnimatorFlags.ShowMirrorImage); + menu.AddItem(new GUIContent("Auto Loop Playback"), flagSettings.HasFlag(AnimatorFlags.AutoLoopPlayback), OnPrefSelected, AnimatorFlags.AutoLoopPlayback); + menu.ShowAsContext(); + } + + private static void OnPrefSelected(object obj) + { + AnimatorFlags f = (AnimatorFlags)obj; + if (flagSettings.HasFlag(f)) + flagSettings ^= f; + else + flagSettings |= f; + + if (WorkingClip) + { + if (f == AnimatorFlags.AnimateOnTheSpot || f == AnimatorFlags.ShowMirrorImage) + { + SetClipSettings(WorkingClip); + ResetAnimationPlayer(); + } + } + } + + // Time Slider + public static void ScrubTimeline() + { + if (EditorApplication.isPlaying) + { + CharacterAnimator.Play(controlStateHash, 0, time); + } + else + { + UpdateAnimator(); + } + } + + // "Time index. Accepts numerical input." + private static void ParseTimeInput() + { + float parsedTime; + if (float.TryParse(realTime, out parsedTime)) + { + if (parsedTime > WorkingClip.length) + parsedTime = WorkingClip.length; + if (parsedTime < 0f) + parsedTime = 0f; + + time = parsedTime / WorkingClip.length; + } + else + { + realTime = TimeText(); + } + if (EditorApplication.isPlaying) + { + CharacterAnimator.Play(controlStateHash, 0, time); + } + } + + // "Animation.FirstKey" + private static void FirstFrameButton() + { + play = false; + time = 0f; + + if (EditorApplication.isPlaying) + { + CharacterAnimator.Play(controlStateHash, 0, time); + CharacterAnimator.SetFloat(paramDirection, 0f); + } + else + { + UpdateAnimator(); + } + } + + // "Animation.PrevKey" + private static void PrevFrameButton() + { + play = false; + time -= 0.0166f / WorkingClip.length; + + if (EditorApplication.isPlaying) + { + CharacterAnimator.Play(controlStateHash, 0, time); + CharacterAnimator.SetFloat(paramDirection, 0f); + } + else + { + UpdateAnimator(); + } + } + + // "Animation.Play" + private static void PlayButton() + { + play = true; + if (EditorApplication.isPlaying) + { + CharacterAnimator.Play(controlStateHash, 0, time); + CharacterAnimator.SetFloat(paramDirection, playbackSpeed); + } + else + { + CharacterAnimator.Update(time); + CharacterAnimator.Play(controlStateHash, 0, time); + } + } + + // "PauseButton" + private static void PauseButton() + { + play = false; + if (EditorApplication.isPlaying) + { + CharacterAnimator.SetFloat(paramDirection, 0f); + } + } + + // "Animation.Play" / "PauseButton" + private static void PlayPauseButton() + { + if (play) + PauseButton(); + else + PlayButton(); + } + + // "Animation.NextKey" + private static void NextFrameButton() + { + play = false; + time += 0.0166f / WorkingClip.length; + + if (EditorApplication.isPlaying) + { + CharacterAnimator.Play(controlStateHash, 0, time); + CharacterAnimator.SetFloat(paramDirection, 0f); + } + else + { + UpdateAnimator(); + } + } + + // "Animation.LastKey" + private static void LastFrameButton() + { + play = false; + time = 1f; + + if (EditorApplication.isPlaying) + { + CharacterAnimator.Play(controlStateHash, 0, time); + CharacterAnimator.SetFloat(paramDirection, 0f); + } + else + { + UpdateAnimator(); + } + } + + private static void ApplicationPlayToggle() + { + // button to enter play mode and retain scene view + // + // if the application is not playing and will enter play mode: + // set the flag to true + // callback will focus the view back to the scene window + // if the application is playing: + // set the flag to false + // entering play mode maually wont cause callback to refocus on the scene + if (!EditorApplication.isPlaying) + Util.SerializeBoolToEditorPrefs(true, WindowManager.sceneFocus); + + EditorApplication.isPlaying = !EditorApplication.isPlaying; + } + + public static void UpdateAnimator() + { + if (EditorApplication.isPlaying || CharacterAnimator == null) return; + if (CharacterAnimator.runtimeAnimatorController.name == overrideName) + { + CharacterAnimator.Update(0f); + CharacterAnimator.Play(controlStateHash, 0, time); + } + } + + private static string TimeText() + { + return string.Format("{0}s", (time * WorkingClip.length).ToString("0.00")); + } + + private static string PlaybackText() + { + return string.Format("{0}x", playbackSpeed.ToString("0.00")); + } + + #endregion Button Events/Functions + + #region Pose reset and bone tracking + public static void ResetCharacterPose() + { + bool canFindAvatar = false; + if (CharacterAnimator != null) + { + if (CharacterAnimator.avatar != null) + { + canFindAvatar = true; + } + } + if (!canFindAvatar) + { + if (showMessages) Debug.LogWarning("No Avatar found to reset pose to."); + return; + } + + Avatar characterAvatar = CharacterAnimator.avatar; + SkeletonBone[] characterBones = characterAvatar.humanDescription.skeleton; // array of all imported objects now in the prefab (has CC names) in T-pose + Transform[] prefabObjects = CharacterAnimator.gameObject.GetComponentsInChildren(); + + int boneIndex; + + foreach (string humanBoneName in HumanTrait.BoneName) + { + // find the characaterBones array indices corresponding to the mechanim bones (listed in HumanTrait.BoneName + boneIndex = FindSkeletonBoneIndex(FindSkeletonBoneName(humanBoneName, characterAvatar), characterBones); + + // iterate through all the transforms in the prefab and when a mechanim bone is matched - set it's transform to that in the correspoding skeletonbone struct (obtained from the avatar) + if (boneIndex != -1) + { + foreach (Transform t in prefabObjects) + { + if (t.name == characterBones[boneIndex].name) + { + t.localPosition = characterBones[boneIndex].position; + t.localRotation = characterBones[boneIndex].rotation; + t.localScale = characterBones[boneIndex].scale; + } + } + } + } + } + + private static int FindSkeletonBoneIndex(string skeletonBoneName, SkeletonBone[] bones) + { + for (int i = 0; i < bones.Length; i++) + { + if (bones[i].name.Equals(skeletonBoneName, System.StringComparison.InvariantCultureIgnoreCase)) + return i; + } + return -1; + } + + public static string FindSkeletonBoneName(string humanBoneName, Avatar avatar) + { + for (int i = 0; i < avatar.humanDescription.human.Length; i++) + { + if (avatar.humanDescription.human[i].humanName.Equals(humanBoneName, System.StringComparison.InvariantCultureIgnoreCase)) + return avatar.humanDescription.human[i].boneName; + } + return boneNotFound; + } + + public class BoneItem + { + public string humanBoneName; + public string skeletonBoneName; + public bool selected; + + public BoneItem(string humanBoneName, string skeletonBoneName) + { + this.humanBoneName = humanBoneName; + this.skeletonBoneName = skeletonBoneName; + this.selected = false; + } + } + + private static void MakeBoneMenuList() + { + boneItemList = new List(); + + foreach (string boneName in orderedHumanBones) + { + string skeletonBoneName = FindSkeletonBoneName(boneName, CharacterAnimator.avatar); + if (skeletonBoneName != boneNotFound) + boneItemList.Add(new BoneItem(boneName, skeletonBoneName)); + //boneItemList.Add(new BoneItem(boneName, FindSkeletonBoneName(boneName, CharacterAnimator.avatar))); + } + } + + private static void GenerateBoneMenu() + { + if (boneItemList == null) + MakeBoneMenuList(); + + GenericMenu menu = new GenericMenu(); + foreach (BoneItem boneItem in boneItemList) + { + menu.AddItem(new GUIContent(boneItem.humanBoneName), boneItem.selected, BoneMenuCallback, boneItem); + } + menu.ShowAsContext(); + } + + private static void BoneMenuCallback(object obj) + { + DeselectAllBones(); + + BoneItem item = obj as BoneItem; + if (TrySelectBone(item)) + { + isTracking = true; + TrackBone(item); + } + } + + private static void TrackBone(BoneItem boneItem) + { + SceneView scene = SceneView.lastActiveSceneView; + GameObject g = GameObject.Find(boneItem.skeletonBoneName); + Selection.activeGameObject = g; + lastTracked = g; + scene.FrameSelected(true, true); + scene.FrameSelected(true, true); + scene.Repaint(); + } + + public static void ReEstablishTracking(string humanBoneName) + { + //if (boneItemList == null) + MakeBoneMenuList(); + foreach (BoneItem boneItem in boneItemList) + { + if (boneItem.humanBoneName == humanBoneName) + { + boneItem.selected = true; + isTracking = true; + TrackBone(boneItem); + return; + } + } + } + + private static bool TrySelectBone(BoneItem boneSelection) + { + int idx = boneItemList.FindIndex(x => x.humanBoneName == boneSelection.humanBoneName); + bool select = (idx != -1); + if (select) + boneItemList[idx].selected = true; + + return select; + } + + private static bool CheckTackingStatus() + { + return Selection.activeGameObject == lastTracked; + } + + private static void CancelBoneTracking(bool refocusScene) + { + if (refocusScene) + StopTracking(); + + DeselectAllBones(); + isTracking = false; + } + + private static void StopTracking() + { + if (isTracking) + { + SceneView scene = SceneView.lastActiveSceneView; + scene.FrameSelected(false, false); + scene.FrameSelected(false, false); + } + } + + private static void DeselectAllBones() + { + if (boneItemList == null) return; + foreach (BoneItem boneItem in boneItemList) + { + boneItem.selected = false; + } + } + + private static string[] orderedHumanBones = + { + "LeftEye", + "RightEye", + "Jaw", + "Head", + "Neck", + "LeftShoulder", + "RightShoulder", + "LeftUpperArm", + "RightUpperArm", + "LeftLowerArm", + "RightLowerArm", + "LeftHand", + "RightHand", + "UpperChest", + "Chest", + "Spine", + "Hips", + "LeftUpperLeg", + "RightUpperLeg", + "LeftLowerLeg", + "RightLowerLeg", + "LeftFoot", + "RightFoot", + "LeftToes", + "RightToes" + /* + "Left Thumb Proximal", + "Left Thumb Intermediate", + "Left Thumb Distal", + "Left Index Proximal", + "Left Index Intermediate", + "Left Index Distal", + "Left Middle Proximal", + "Left Middle Intermediate", + "Left Middle Distal", + "Left Ring Proximal", + "Left Ring Intermediate", + "Left Ring Distal", + "Left Little Proximal", + "Left Little Intermediate", + "Left Little Distal", + "Right Thumb Proximal", + "Right Thumb Intermediate", + "Right Thumb Distal", + "Right Index Proximal", + "Right Index Intermediate", + "Right Index Distal", + "Right Middle Proximal", + "Right Middle Intermediate", + "Right Middle Distal", + "Right Ring Proximal", + "Right Ring Intermediate", + "Right Ring Distal", + "Right Little Proximal", + "Right Little Intermediate", + "Right Little Distal" + */ + }; + + #endregion Pose reset and bone tracking + public static void SampleOnce() + { + if (CharacterAnimator && WorkingClip) + { + if (!AnimationMode.InAnimationMode()) AnimationMode.StartAnimationMode(); + AnimationMode.BeginSampling(); + AnimationMode.SampleAnimationClip(CharacterAnimator.gameObject, WorkingClip, time); + AnimationMode.EndSampling(); + } + } + + #region Update + private static void PlayStateChangeCallback(PlayModeStateChange state) + { + wasPlaying = play; + switch (state) + { + case PlayModeStateChange.ExitingEditMode: + { + play = false; + Util.TrySerializeAssetToEditorPrefs(OriginalClip, WindowManager.clipKey); + Util.SerializeIntToEditorPrefs(controlStateHash, WindowManager.controlStateHashKey); + Util.SerializeFloatToEditorPrefs(time, WindowManager.timeKey); + Util.SerializeBoolToEditorPrefs(isTracking, WindowManager.trackingStatusKey); + if (isTracking) + { + foreach (BoneItem boneItem in boneItemList) + { + if (boneItem.selected) + { + Util.SerializeStringToEditorPrefs(boneItem.humanBoneName, WindowManager.lastTrackedBoneKey); + } + + } + } + + //replace original animator controller + ResetToBaseAnimatorController(); + + break; + } + case PlayModeStateChange.EnteredPlayMode: + { + break; + } + case PlayModeStateChange.ExitingPlayMode: + { + break; + } + case PlayModeStateChange.EnteredEditMode: + { + break; + } + } + } + public static int delayFrames = 0; + private static void UpdateCallback() + { + if (delayFrames > 0) + { + delayFrames--; + if (delayFrames == 0) + AnimPlayerGUI.ScrubTimeline(); + + return; + } + + if (updateTime == 0f) updateTime = EditorApplication.timeSinceStartup; + deltaTime = EditorApplication.timeSinceStartup - updateTime; + updateTime = EditorApplication.timeSinceStartup; + + AdjustEyes(); + + if (WorkingClip && CharacterAnimator) + { + if (play) + { + double frameDuration = 1.0f / WorkingClip.frameRate; + + time += ((float)deltaTime / WorkingClip.length) * playbackSpeed; + frameTime += deltaTime; + if (time >= 1) + { + time = flagSettings.HasFlag(AnimatorFlags.AutoLoopPlayback) ? 0f : 1f; + if (EditorApplication.isPlaying) + CharacterAnimator.Play(controlStateHash, 0, time); + } + + if (time < 0) + { + time = flagSettings.HasFlag(AnimatorFlags.AutoLoopPlayback) ? 1f : 0f; + if (EditorApplication.isPlaying) + CharacterAnimator.Play(controlStateHash, 0, time); + } + + if (frameTime < frameDuration) + return; + + frameTime = 0f; + } + else + { + frameTime = 1f; + } + + if (current != time) + { + UpdateAnimator(); + //Repaint(); // repaint the gui to smooth the timer + slider display + current = time; + } + } + } + + + #endregion Update + + private static void UpdateDelegate() { if (updateTime == 0f) updateTime = EditorApplication.timeSinceStartup; diff --git a/Editor/AnimPlayerOverlay.cs b/Editor/AnimPlayerOverlay.cs index 64552f3..2134b3c 100644 --- a/Editor/AnimPlayerOverlay.cs +++ b/Editor/AnimPlayerOverlay.cs @@ -97,7 +97,7 @@ public override void OnWillBeDestroyed() Hide(); createdOverlays.Remove(this); } - + Debug.Log("OnWillBeDestroyed"); base.OnWillBeDestroyed(); } diff --git a/Editor/AnimPlayerWindow.cs b/Editor/AnimPlayerWindow.cs index 60a212a..68a7e8d 100644 --- a/Editor/AnimPlayerWindow.cs +++ b/Editor/AnimPlayerWindow.cs @@ -36,7 +36,7 @@ public class AnimPlayerWindow public static void OnSceneGUI(SceneView sceneView) { height = 72f; - if (AnimPlayerGUI.AnimFoldOut) height += 84f; + if (AnimPlayerGUI.AnimFoldOut) height += 140f; if (AnimPlayerGUI.FaceFoldOut) height += 90f; float x = sceneView.position.width - width - xpadding; @@ -45,15 +45,35 @@ public static void OnSceneGUI(SceneView sceneView) sceneView.autoRepaintOnSceneChange = true; var windowOverlayRect = new Rect(x, y, width, height); - GUILayout.Window("Animation Playback".GetHashCode(), windowOverlayRect, DoWindow, "Character Preview Tools"); - } + + //GUILayout.Window("Animation Playback".GetHashCode(), windowOverlayRect, DoWindow, "Character Preview Tools"); + + + // to counter: Resolve of invalid GC handle. The handle is from a previous domain. The resolve operation is skipped. + + string name = EditorApplication.isPlaying ? "Character Preview Tools (Runtime)" : "Character Preview Tools"; + int id = EditorApplication.isPlaying ? "Animation Playback".GetHashCode() : "Animation Playback (Runtime)".GetHashCode(); + if (EditorApplication.isPlaying) + { + if (!doneOnce) + { + GUILayout.Window(id, new Rect(), Empty, name); + doneOnce = true; + } + } + GUILayout.Window(id, windowOverlayRect, DoWindow, name); + } + private static bool doneOnce = false; public static void ShowPlayer() { if (!isShown) { + Debug.Log("WINDOW: SHOWING PLAYER"); + SceneView.duringSceneGui -= AnimPlayerWindow.OnSceneGUI; SceneView.duringSceneGui += AnimPlayerWindow.OnSceneGUI; isShown = true; + doneOnce = false; } } @@ -61,6 +81,7 @@ public static void HidePlayer() { if (isShown) { + Debug.Log("WINDOW: HIDING PLAYER"); SceneView.duringSceneGui -= AnimPlayerWindow.OnSceneGUI; AnimPlayerGUI.CleanUp(); @@ -72,6 +93,11 @@ public static void DoWindow(int id) { AnimPlayerGUI.DrawPlayer(); AnimPlayerGUI.DrawFacialMorph(); - } + } + + public static void Empty(int id) + { + Debug.Log("Showing " + id); + } } } diff --git a/Editor/AnimRetargetGUI.cs b/Editor/AnimRetargetGUI.cs index 76e3156..341fc2d 100644 --- a/Editor/AnimRetargetGUI.cs +++ b/Editor/AnimRetargetGUI.cs @@ -17,7 +17,6 @@ */ using System; -using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Linq; @@ -474,8 +473,9 @@ public static void DrawRetargeter() GameObject fbxAsset = Util.FindRootPrefabAssetFromSceneObject(scenePrefab); if (fbxAsset) { - string assetPath = GenerateClipAssetPath(OriginalClip, fbxAsset); - WriteAnimationToAssetDatabase(WorkingClip, assetPath); + string characterFbxPath = AssetDatabase.GetAssetPath(fbxAsset); + string assetPath = GenerateClipAssetPath(OriginalClip, characterFbxPath); + WriteAnimationToAssetDatabase(WorkingClip, assetPath, true); } } GUILayout.EndVertical(); @@ -1241,15 +1241,12 @@ static void RetargetBlendShapes(AnimationClip originalClip, AnimationClip workin } } - static string GenerateClipAssetPath(AnimationClip originalClip, GameObject fbxAsset, string prefix = "", bool overwrite = false) + static string GenerateClipAssetPath(AnimationClip originalClip, string characterFbxPath, string prefix = "", bool overwrite = false) { - if (!(originalClip && fbxAsset)) return null; + if (!originalClip || string.IsNullOrEmpty(characterFbxPath)) return null; - string fbxPath = AssetDatabase.GetAssetPath(fbxAsset); - if (string.IsNullOrEmpty(fbxPath)) return null; - - string characterName = Path.GetFileNameWithoutExtension(fbxPath); - string fbxFolder = Path.GetDirectoryName(fbxPath); + string characterName = Path.GetFileNameWithoutExtension(characterFbxPath); + string fbxFolder = Path.GetDirectoryName(characterFbxPath); string animFolder = Path.Combine(fbxFolder, ANIM_FOLDER_NAME, characterName); Util.EnsureAssetsFolderExists(animFolder); string clipName = originalClip.name; @@ -1282,7 +1279,10 @@ static string GenerateClipAssetPath(AnimationClip originalClip, GameObject fbxAs return assetPath; } - static AnimationClip WriteAnimationToAssetDatabase(AnimationClip workingClip, string assetPath) + + + + static AnimationClip WriteAnimationToAssetDatabase(AnimationClip workingClip, string assetPath, bool originalSettings = false) { if (string.IsNullOrEmpty(assetPath)) return null; @@ -1290,6 +1290,33 @@ static AnimationClip WriteAnimationToAssetDatabase(AnimationClip workingClip, st var output = Object.Instantiate(workingClip); // clone so that workingClip isn't locked to an on-disk asset AnimationClip outputClip = output as AnimationClip; + + if (originalSettings) + { + // **Addition** for the edit mode animator player: the clip settings of the working clip + // may contain user set flags that are for evaluation purposes only (e.g. loopBlendPositionXZ) + // the original clip's settings should be copied to the output clip and the loop flag set as + // per the user preference to auto loop the animation. + + // record the user preferred loop status + AnimationClipSettings outputClipSettings = AnimationUtility.GetAnimationClipSettings(outputClip); + bool isLooping = outputClipSettings.loopTime; + + // obtain the original settings + AnimationClipSettings originalClipSettings = AnimationUtility.GetAnimationClipSettings(OriginalClip); + + // re-impose the loop status + originalClipSettings.loopTime = isLooping; + + //update the output clip with the looping modified original settings + AnimationUtility.SetAnimationClipSettings(outputClip, outputClipSettings); + + // the correct settings can now be written to disk - but the in memory copy used by the + // player/re-tartgeter will be untouched so end users dont see a behaviour change after saving + + // **End of addition** + } + AssetDatabase.CreateAsset(outputClip, assetPath); AnimationClip asset = AssetDatabase.LoadAssetAtPath(assetPath); @@ -1426,12 +1453,12 @@ private static void ExtractPose() System.IO.File.WriteAllText(path, pathString); } - public static void GenerateCharacterTargetedAnimations(GameObject characterFbx, - GameObject prefabAsset, bool replace) + public static void GenerateCharacterTargetedAnimations(string motionAssetPath, + GameObject prefabAsset, bool replaceIfExists) { - AnimationClip[] clips = Util.GetAllAnimationClipsFromCharacter(characterFbx); + AnimationClip[] clips = Util.GetAllAnimationClipsFromCharacter(motionAssetPath); - if (!prefabAsset) prefabAsset = Util.FindCharacterPrefabAsset(characterFbx); + if (!prefabAsset) prefabAsset = Util.FindCharacterPrefabAsset(motionAssetPath); if (!prefabAsset) return; string firstPath = null; @@ -1441,12 +1468,12 @@ public static void GenerateCharacterTargetedAnimations(GameObject characterFbx, int index = 0; foreach (AnimationClip clip in clips) { - string assetPath = GenerateClipAssetPath(clip, characterFbx, RETARGET_SOURCE_PREFIX, true); + string assetPath = GenerateClipAssetPath(clip, motionAssetPath, RETARGET_SOURCE_PREFIX, true); if (string.IsNullOrEmpty(firstPath)) firstPath = assetPath; - if (File.Exists(assetPath) && !replace) continue; + if (File.Exists(assetPath) && !replaceIfExists) continue; AnimationClip workingClip = AnimPlayerGUI.CloneClip(clip); RetargetBlendShapes(clip, workingClip, prefabAsset, false); - AnimationClip asset = WriteAnimationToAssetDatabase(workingClip, assetPath); + AnimationClip asset = WriteAnimationToAssetDatabase(workingClip, assetPath, false); index++; } @@ -1456,6 +1483,10 @@ public static void GenerateCharacterTargetedAnimations(GameObject characterFbx, } } + /// + /// Tries to get the retargeted version of the animation clip from the given source animation clip, + /// usually from the original character fbx. + /// public static AnimationClip TryGetRetargetedAnimationClip(GameObject fbxAsset, AnimationClip clip) { try diff --git a/Editor/CharacterInfo.cs b/Editor/CharacterInfo.cs index 73a7a3d..328849d 100644 --- a/Editor/CharacterInfo.cs +++ b/Editor/CharacterInfo.cs @@ -28,7 +28,15 @@ public class CharacterInfo public enum ProcessingType { None, Basic, HighQuality } public enum EyeQuality { None, Basic, Parallax, Refractive } public enum HairQuality { None, Default, TwoPass, Coverage } - public enum ShaderFeatureFlags { NoFeatures = 0, Tessellation = 1, ClothPhysics = 2, HairPhysics = 4, SpringBoneHair = 8, WrinkleMaps = 16 } + public enum ShaderFeatureFlags + { + NoFeatures = 0, + Tessellation = 1, + ClothPhysics = 2, + HairPhysics = 4, + SpringBoneHair = 8, + WrinkleMaps = 16, + } public enum RigOverride { None = 0, Generic, Humanoid } @@ -45,6 +53,9 @@ public enum RigOverride { None = 0, Generic, Humanoid } public bool animationSetup = false; public int animationRetargeted = 0; + public bool selectedInList; + public bool settingsChanged; + // these are the settings the character is currently set to build private ProcessingType logType = ProcessingType.None; private EyeQuality qualEyes = EyeQuality.Parallax; @@ -52,7 +63,6 @@ public enum RigOverride { None = 0, Generic, Humanoid } public RigOverride UnknownRigType { get; set; } private bool bakeCustomShaders = true; private bool bakeSeparatePrefab = true; - private bool useTessellation = false; private GameObject prefabAsset; public struct GUIDRemap @@ -158,7 +168,7 @@ public MaterialQuality BuildQuality public bool FeatureUseTessellation => (ShaderFlags & ShaderFeatureFlags.Tessellation) > 0; public bool FeatureUseClothPhysics => (ShaderFlags & ShaderFeatureFlags.ClothPhysics) > 0; public bool FeatureUseHairPhysics => (ShaderFlags & ShaderFeatureFlags.HairPhysics) > 0; - //public bool FeatureUseSpringBones => (ShaderFlags & ShaderFeatureFlags.SpringBones) > 0; + //public bool FeatureUseSpringBones => (ShaderFlags & ShaderFeatureFlags.SpringBones) > 0; public bool BasicMaterials => logType == ProcessingType.Basic; public bool HQMaterials => logType == ProcessingType.HighQuality; public EyeQuality QualEyes { get { return qualEyes; } set { qualEyes = value; } } @@ -177,12 +187,11 @@ public MaterialQuality BuildQuality private EyeQuality builtQualEyes = EyeQuality.Parallax; private HairQuality builtQualHair = HairQuality.TwoPass; private bool builtBakeCustomShaders = true; - private bool builtBakeSeparatePrefab = true; - private bool builtTessellation = false; + private bool builtBakeSeparatePrefab = true; public ShaderFeatureFlags BuiltShaderFlags { get; private set; } = ShaderFeatureFlags.NoFeatures; public bool BuiltFeatureWrinkleMaps => (BuiltShaderFlags & ShaderFeatureFlags.WrinkleMaps) > 0; - public bool BuiltFeatureTessellation => (BuiltShaderFlags & ShaderFeatureFlags.Tessellation) > 0; + public bool BuiltFeatureTessellation => (BuiltShaderFlags & ShaderFeatureFlags.Tessellation) > 0; public bool BuiltBasicMaterials => builtLogType == ProcessingType.Basic; public bool BuiltHQMaterials => builtLogType == ProcessingType.HighQuality; public bool BuiltDualMaterialHair => builtQualHair == HairQuality.TwoPass; @@ -192,7 +201,7 @@ public MaterialQuality BuildQuality public HairQuality BuiltQualHair => builtQualHair; public bool BuiltRefractiveEyes => BuiltQualEyes == EyeQuality.Refractive; public bool BuiltBasicEyes => BuiltQualEyes == EyeQuality.Basic; - public bool BuiltParallaxEyes => BuiltQualEyes == EyeQuality.Parallax; + public bool BuiltParallaxEyes => BuiltQualEyes == EyeQuality.Parallax; public MaterialQuality BuiltQuality => BuiltHQMaterials ? MaterialQuality.High : MaterialQuality.Default; public bool Unprocessed => builtLogType == ProcessingType.None; @@ -205,7 +214,7 @@ public MaterialQuality BuildQuality private GameObject fbx; private QuickJSON jsonData; - private void FixCharSettings() + public void FixCharSettings() { if (logType == ProcessingType.HighQuality && !CanHaveHighQualityMaterials) logType = ProcessingType.Basic; @@ -214,7 +223,13 @@ private void FixCharSettings() qualEyes = EyeQuality.Parallax; if (qualHair == HairQuality.Coverage && Pipeline.isHDRP) - qualHair = HairQuality.Default; + qualHair = HairQuality.Default; + + if ((ShaderFlags & ShaderFeatureFlags.SpringBoneHair) > 0 && + (ShaderFlags & ShaderFeatureFlags.HairPhysics) > 0) + { + ShaderFlags -= ShaderFeatureFlags.SpringBoneHair; + } } public CharacterInfo(string guid) @@ -228,12 +243,27 @@ public CharacterInfo(string guid) if (path.iContains("_lod")) isLOD = true; guidRemaps = new List(); + selectedInList = false; + settingsChanged = false; + if (File.Exists(infoFilepath)) Read(); else Write(); } + public void CopySettings(CharacterInfo from) + { + UnknownRigType = from.UnknownRigType; + logType = from.logType; + qualEyes = from.qualEyes; + qualHair = from.qualHair; + bakeCustomShaders = from.bakeCustomShaders; + bakeSeparatePrefab = from.bakeSeparatePrefab; + ShaderFlags = from.ShaderFlags; + FixCharSettings(); + } + public void ApplySettings() { FixCharSettings(); @@ -244,7 +274,6 @@ public void ApplySettings() builtQualHair = qualHair; builtBakeCustomShaders = bakeCustomShaders; builtBakeSeparatePrefab = bakeSeparatePrefab; - builtTessellation = useTessellation; BuiltShaderFlags = ShaderFlags; } @@ -266,6 +295,39 @@ public bool FbxLoaded get { return fbx != null; } } + public Avatar GetCharacterAvatar() + { + Object[] objects = AssetDatabase.LoadAllAssetsAtPath(path); + foreach (Object obj in objects) + { + if (obj.GetType() == typeof(Avatar)) + { + return obj as Avatar; + } + } + + return null; + } + + public List GetMotionGuids() + { + List motionGuids = new List(); + DirectoryInfo di = new DirectoryInfo(folder); + string prefix = name + "_"; + string suffix = "_Motion.fbx"; + foreach (FileInfo fi in di.GetFiles("*.fbx")) + { + if (fi.Name.iStartsWith(prefix) && fi.Name.iEndsWith(suffix)) + { + string path = Path.Combine(folder, fi.Name); + string guid = AssetDatabase.AssetPathToGUID(path); + motionGuids.Add(guid); + } + } + + return motionGuids; + } + public string GetPrefabsFolder() { return Path.Combine(folder, Importer.PREFABS_FOLDER); diff --git a/Editor/ColliderManagerEditor.cs b/Editor/ColliderManagerEditor.cs index a8a6b99..306b5d3 100644 --- a/Editor/ColliderManagerEditor.cs +++ b/Editor/ColliderManagerEditor.cs @@ -17,10 +17,7 @@ */ using UnityEngine; -using System.Collections; -using System.Collections.Generic; using UnityEditor; -using System.IO; using ColliderSettings = Reallusion.Import.ColliderManager.ColliderSettings; namespace Reallusion.Import diff --git a/Editor/Compute/RLBakeShader.compute b/Editor/Compute/RLBakeShader.compute index 6cb2a44..11ebc14 100644 --- a/Editor/Compute/RLBakeShader.compute +++ b/Editor/Compute/RLBakeShader.compute @@ -55,6 +55,7 @@ #pragma kernel RLHairAO #pragma kernel RLEyeOcclusionDiffuse #pragma kernel RLFlowToNormal +#pragma kernel RLHDRPCorrected #pragma kernel RLChannelPackLinear #pragma kernel RLChannelPackSymmetryLinear @@ -90,6 +91,7 @@ float normalBlendStrength; float sssNormalSoften; TEX2D(Mask); +TEX2D(Detail); TEX2D(MetallicGloss); TEX2D(AO); TEX2D(Metallic); @@ -1637,6 +1639,18 @@ void RLFlowToNormal(uint3 id : SV_DispatchThreadID) Result[id.xy] = color; } +[numthreads(1, 1, 1)] +void RLHDRPCorrected(uint3 id : SV_DispatchThreadID) +{ + float2 uv = GetUV(id.xy); + + float4 mask = SAMPLE(Mask, uv); + float detail = SAMPLE(Detail, uv).g; + mask.b = sqrt(detail); + + Result[id.xy] = mask; +} + diff --git a/Editor/ComputeBake.cs b/Editor/ComputeBake.cs index d684412..a2337a7 100644 --- a/Editor/ComputeBake.cs +++ b/Editor/ComputeBake.cs @@ -885,6 +885,7 @@ private Material BakeSkinMaterial(Material mat, string sourceName) bool useAmplify = characterInfo.BakeCustomShaders && mat.shader.name.iContains("/Amplify/"); bool useTessellation = characterInfo.BuiltFeatureTessellation; bool useWrinkleMaps = characterInfo.BakeCustomShaders && characterInfo.BuiltFeatureWrinkleMaps; + bool useDigitalHuman = characterInfo.BakeCustomShaders && mat.shader.name.iEndsWith("_DH"); if (!IS_HDRP && !useAmplify) sssNormalSoften = 0f; @@ -1064,7 +1065,8 @@ private Material BakeSkinMaterial(Material mat, string sourceName) if (sourceName.iContains("Skin_Head")) materialType = MaterialType.Head; Material templateMaterial = Pipeline.GetTemplateMaterial(sourceName, materialType, - MaterialQuality.Baked, characterInfo, useAmplify, useTessellation, useWrinkleMaps); + MaterialQuality.Baked, characterInfo, + useAmplify, useTessellation, useWrinkleMaps, useDigitalHuman); Material result = CreateBakedMaterial(bakedBaseMap, bakedMaskMap, bakedMetallicGlossMap, bakedAOMap, bakedNormalMap, bakedDetailMask, bakedDetailMap, bakedSubsurfaceMap, bakedThicknessMap, emissionMap, @@ -1138,6 +1140,7 @@ private Material BakeTeethMaterial(Material mat, string sourceName) bool useAmplify = characterInfo.BakeCustomShaders && mat.shader.name.iContains("/Amplify/"); bool useTessellation = characterInfo.BuiltFeatureTessellation; + bool useDigitalHuman = characterInfo.BakeCustomShaders && mat.shader.name.iEndsWith("_DH"); Texture2D bakedBaseMap = diffuse; Texture2D bakedMaskMap = mask; @@ -1199,7 +1202,7 @@ private Material BakeTeethMaterial(Material mat, string sourceName) normalStrength, microNormalTiling, microNormalStrength, emissiveColor, sourceName, Pipeline.GetTemplateMaterial(sourceName, MaterialType.Teeth, - MaterialQuality.Baked, characterInfo, useAmplify, useTessellation)); + MaterialQuality.Baked, characterInfo, useAmplify, useTessellation, useDigitalHuman)); CopyAMPSubsurface(mat, result); @@ -1234,6 +1237,7 @@ private Material BakeTongueMaterial(Material mat, string sourceName) bool useAmplify = characterInfo.BakeCustomShaders && mat.shader.name.iContains("/Amplify/"); bool useTessellation = characterInfo.BuiltFeatureTessellation; + bool useDigitalHuman = characterInfo.BakeCustomShaders && mat.shader.name.iEndsWith("_DH"); Texture2D bakedBaseMap = diffuse; Texture2D bakedMaskMap = mask; @@ -1297,7 +1301,7 @@ private Material BakeTongueMaterial(Material mat, string sourceName) normalStrength, microNormalTiling, microNormalStrength, emissiveColor, sourceName, Pipeline.GetTemplateMaterial(sourceName, MaterialType.Tongue, - MaterialQuality.Baked, characterInfo, useAmplify, useTessellation)); + MaterialQuality.Baked, characterInfo, useAmplify, useTessellation, useDigitalHuman)); CopyAMPSubsurface(mat, result); @@ -1357,6 +1361,7 @@ private Material BakeEyeMaterial(Material mat, string sourceName) bool useAmplify = characterInfo.BakeCustomShaders && mat.shader.name.iContains("/Amplify/"); bool useTessellation = characterInfo.BuiltFeatureTessellation; + bool useDigitalHuman = characterInfo.BakeCustomShaders && mat.shader.name.iEndsWith("_DH"); Texture2D bakedBaseMap = cornea; Texture2D bakedMaskMap = mask; @@ -1446,7 +1451,8 @@ private Material BakeEyeMaterial(Material mat, string sourceName) bakedDetailMask, bakedDetailMap, bakedSubsurfaceMap, bakedThicknessMap, emissionMap, 1f, microNormalTiling, microNormalStrength, emissiveColor, sourceName, isCornea ? Pipeline.GetTemplateMaterial(sourceName, MaterialType.Cornea, - MaterialQuality.Baked, characterInfo, useAmplify, useTessellation) + MaterialQuality.Baked, characterInfo, + useAmplify, useTessellation, useDigitalHuman) : Pipeline.GetTemplateMaterial(sourceName, MaterialType.Eye, MaterialQuality.Baked, characterInfo)); @@ -1486,7 +1492,8 @@ private Material BakeEyeMaterial(Material mat, string sourceName) - private Material BakeHairMaterial(Material mat, string sourceName, out Material firstPass, out Material secondPass) + private Material BakeHairMaterial(Material mat, string sourceName, + out Material firstPass, out Material secondPass) { Texture2D diffuse = GetMaterialTexture(mat, "_DiffuseMap"); Texture2D mask = GetMaterialTexture(mat, "_MaskMap"); @@ -1560,6 +1567,7 @@ private Material BakeHairMaterial(Material mat, string sourceName, out Material bool useAmplify = characterInfo.BakeCustomShaders && mat.shader.name.iContains("/Amplify/"); bool useTessellation = characterInfo.BuiltFeatureTessellation; bool useWrinkleMaps = characterInfo.BuiltFeatureWrinkleMaps; + bool useDigitalHuman = characterInfo.BakeCustomShaders && mat.shader.name.iEndsWith("_DH"); Texture2D bakedBaseMap = diffuse; Texture2D bakedMaskMap = mask; @@ -1690,14 +1698,14 @@ private Material BakeHairMaterial(Material mat, string sourceName, out Material normalStrength, 1f, 1f, emissiveColor, sourceName + "_1st_Pass", Pipeline.GetUpgradedTemplateMaterial(sourceName, Pipeline.MATERIAL_BAKED_HAIR_CUSTOM_1ST_PASS, - MaterialQuality.Baked, useAmplify, useTessellation, useWrinkleMaps)); + MaterialQuality.Baked, useAmplify, useTessellation, useWrinkleMaps, useDigitalHuman)); secondPass = CreateBakedMaterial(bakedBaseMap, bakedMaskMap, bakedMetallicGlossMap, bakedAOMap, bakedNormalMap, null, null, null, null, emissionMap, normalStrength, 1f, 1f, emissiveColor, sourceName + "_2nd_Pass", Pipeline.GetUpgradedTemplateMaterial(sourceName, Pipeline.MATERIAL_BAKED_HAIR_CUSTOM_2ND_PASS, - MaterialQuality.Baked, useAmplify, useTessellation, useWrinkleMaps)); + MaterialQuality.Baked, useAmplify, useTessellation, useWrinkleMaps, useDigitalHuman)); // multi material pass hair is custom baked shader only: SetCustom(firstPass); @@ -1714,7 +1722,8 @@ private Material BakeHairMaterial(Material mat, string sourceName, out Material normalStrength, 1f, 1f, emissiveColor, sourceName, Pipeline.GetTemplateMaterial(sourceName, MaterialType.Hair, - MaterialQuality.Baked, characterInfo, useAmplify, useTessellation, useWrinkleMaps)); + MaterialQuality.Baked, characterInfo, + useAmplify, useTessellation, useWrinkleMaps, useDigitalHuman)); SetCustom(result); return result; @@ -1746,13 +1755,15 @@ private Material BakeHairMaterial(Material mat, string sourceName, out Material null, null, null, null, emissionMap, normalStrength, 1f, 1f, emissiveColor, sourceName + "_1st_Pass", - Pipeline.GetUpgradedTemplateMaterial(sourceName, Pipeline.MATERIAL_BAKED_HAIR_1ST_PASS, MaterialQuality.Baked, useAmplify, useTessellation, useWrinkleMaps)); + Pipeline.GetUpgradedTemplateMaterial(sourceName, Pipeline.MATERIAL_BAKED_HAIR_1ST_PASS, + MaterialQuality.Baked, useAmplify, useTessellation, useWrinkleMaps, useDigitalHuman)); secondPass = CreateBakedMaterial(bakedBaseMap, bakedMaskMap, bakedMetallicGlossMap, bakedAOMap, bakedNormalMap, null, null, null, null, emissionMap, normalStrength, 1f, 1f, emissiveColor, sourceName + "_2nd_Pass", - Pipeline.GetUpgradedTemplateMaterial(sourceName, Pipeline.MATERIAL_BAKED_HAIR_2ND_PASS, MaterialQuality.Baked, useAmplify, useTessellation, useWrinkleMaps)); + Pipeline.GetUpgradedTemplateMaterial(sourceName, Pipeline.MATERIAL_BAKED_HAIR_2ND_PASS, + MaterialQuality.Baked, useAmplify, useTessellation, useWrinkleMaps, useDigitalHuman)); SetBasic(firstPass); alphaClip = 0.01f; @@ -1768,7 +1779,8 @@ private Material BakeHairMaterial(Material mat, string sourceName, out Material normalStrength, 1f, 1f, emissiveColor, sourceName, Pipeline.GetTemplateMaterial(sourceName, MaterialType.Hair, - MaterialQuality.Baked, characterInfo)); + MaterialQuality.Baked, characterInfo, + useAmplify, useTessellation, useWrinkleMaps, useDigitalHuman)); SetBasic(result); return result; @@ -3271,6 +3283,12 @@ private Texture2D BakeEyeOcclusionDiffuseMap(float occlusionStrength, float occl return null; } + public Texture2D BakeCorrectedHDRPMap(Texture2D mask, Texture2D detail, string name) + { + Texture2D bakedThickness = BakeHDRPMap(mask, detail, name); + return bakedThickness; + } + public Texture2D BakeDefaultSkinThicknessMap(Texture2D thickness, string name) { Texture2D bakedThickness = BakeThicknessMap(thickness, 0f, 1.0f, Color.white, Texture2D.whiteTexture, true, name); @@ -3306,6 +3324,30 @@ public Texture2D BakeFlowMapToNormalMap(Texture2D flowMap, Vector3 tangentVector return null; } + + private Texture2D BakeHDRPMap(Texture2D mask, Texture2D detail, + string name, string kernelName = "RLHDRPCorrected") + { + Vector2Int maxSize = GetMaxSize(mask); + ComputeBakeTexture bakeTarget = + new ComputeBakeTexture(maxSize, texturesFolder, name); + + ComputeShader bakeShader = Util.FindComputeShader(COMPUTE_SHADER); + if (bakeShader) + { + mask = CheckMask(mask); + detail = CheckMask(detail); + + int kernel = bakeShader.FindKernel(kernelName); + bakeTarget.Create(bakeShader, kernel); + bakeShader.SetTexture(kernel, "Mask", mask); + bakeShader.SetTexture(kernel, "Detail", detail); + bakeShader.Dispatch(kernel, bakeTarget.width, bakeTarget.height, 1); + return bakeTarget.SaveAndReimport(); + } + + return null; + } } } diff --git a/Editor/Importer.cs b/Editor/Importer.cs index bcd6801..4f275e1 100644 --- a/Editor/Importer.cs +++ b/Editor/Importer.cs @@ -43,6 +43,7 @@ public class Importer private List doneTextureGUIDS = new List(); private Dictionary bakedDetailMaps; private Dictionary bakedThicknessMaps; + private Dictionary bakedHDRPMaps; private readonly BaseGeneration generation; private readonly bool blenderProject; @@ -96,6 +97,21 @@ public static bool USE_AMPLIFY_SHADER } } + public static bool USE_DIGITAL_HUMAN_SHADER + { + get + { + if (EditorPrefs.HasKey("RL_Importer_Use_Digital_Human_Shaders")) + return EditorPrefs.GetBool("RL_Importer_Use_Digital_Human_Shaders"); + return false; + } + + set + { + EditorPrefs.SetBool("RL_Importer_Use_Digital_Human_Shaders", value); + } + } + public static bool ANIMPLAYER_ON_BY_DEFAULT { get @@ -111,6 +127,21 @@ public static bool ANIMPLAYER_ON_BY_DEFAULT } } + public static bool USE_SELF_COLLISION + { + get + { + if (EditorPrefs.HasKey("RL_Importer_Use_Self_Collision")) + return EditorPrefs.GetBool("RL_Importer_Use_Self_Collision"); + return false; + } + + set + { + EditorPrefs.SetBool("RL_Importer_Use_Self_Collision", value); + } + } + public static bool RECONSTRUCT_FLOW_NORMALS { get @@ -193,9 +224,10 @@ public Importer(CharacterInfo info) bakedDetailMaps = new Dictionary(); bakedThicknessMaps = new Dictionary(); + bakedHDRPMaps = new Dictionary(); } - public GameObject Import() + public GameObject Import(bool batchMode = false) { // make sure custom diffusion profiles are installed Pipeline.AddDiffusionProfilesHDRP(); @@ -325,7 +357,7 @@ public GameObject Import() int animationRetargeted = characterInfo.DualMaterialHair ? 2 : 1; bool replace = characterInfo.animationRetargeted != animationRetargeted; if (replace) Util.LogInfo("Retargeting all imported animations."); - AnimRetargetGUI.GenerateCharacterTargetedAnimations(fbx, prefabAsset, replace); + AnimRetargetGUI.GenerateCharacterTargetedAnimations(fbxPath, prefabAsset, replace); characterInfo.animationRetargeted = animationRetargeted; // create default animator if there isn't one. @@ -333,7 +365,23 @@ public GameObject Import() Util.LogAlways("Done building materials for character " + characterName + "!"); - Selection.activeObject = prefabAsset; + + + List motionGuids = characterInfo.GetMotionGuids(); + if (motionGuids.Count > 0) + { + Avatar sourceAvatar = characterInfo.GetCharacterAvatar(); + if (sourceAvatar) + { + foreach (string guid in motionGuids) + { + ProcessMotionFbx(guid, sourceAvatar, prefabAsset); + } + } + } + + if (!batchMode) Selection.activeObject = prefabAsset; + else Selection.activeObject = null; //System.Media.SystemSounds.Asterisk.Play(); @@ -444,6 +492,11 @@ private void ProcessObjectPrepass(Renderer renderer) PrepDefaultMap(sourceName, "MicroN", matJson, "Custom Shader/Image/MicroNormal", FLAG_NORMAL); } } + + if (materialType == MaterialType.Skin || materialType == MaterialType.Head) + { + FixHDRPMap(sharedMat, sourceName, matJson); + } } } } @@ -672,8 +725,12 @@ private MaterialType GetMaterialType(GameObject obj, Material mat, string source private Material CreateRemapMaterial(MaterialType materialType, Material sharedMaterial, string sourceName) { // get the template material. - Material templateMaterial = Pipeline.GetTemplateMaterial(sourceName, materialType, characterInfo.BuildQuality, - characterInfo, USE_AMPLIFY_SHADER, characterInfo.FeatureUseTessellation, characterInfo.FeatureUseWrinkleMaps); + Material templateMaterial = Pipeline.GetTemplateMaterial(sourceName, materialType, + characterInfo.BuildQuality, + characterInfo, USE_AMPLIFY_SHADER, + characterInfo.FeatureUseTessellation, + characterInfo.FeatureUseWrinkleMaps, + USE_DIGITAL_HUMAN_SHADER); // get the appropriate shader to use Shader shader; @@ -1417,10 +1474,18 @@ private void ConnectHQSkinMaterial(GameObject obj, string sourceName, Material s ConnectTextureTo(sourceName, mat, "_NormalMap", "Normal", matJson, "Textures/Normal", - FLAG_NORMAL); + FLAG_NORMAL); - ConnectTextureTo(sourceName, mat, "_MaskMap", "HDRP", - matJson, "Textures/HDRP"); + // try to use corrected HDRP mask mask + if (bakedHDRPMaps.TryGetValue(sharedMat, out Texture2D bakedHDRP)) + { + mat.SetTextureIf("_MaskMap", bakedHDRP); + } + else + { + ConnectTextureTo(sourceName, mat, "_MaskMap", "HDRP", + matJson, "Textures/HDRP"); + } ConnectTextureTo(sourceName, mat, "_MetallicAlphaMap", "MetallicAlpha", matJson, "Textures/MetallicAlpha"); @@ -1434,6 +1499,9 @@ private void ConnectHQSkinMaterial(GameObject obj, string sourceName, Material s ConnectTextureTo(sourceName, mat, "_ThicknessMap", "TransMap", matJson, "Custom Shader/Image/Transmission Map"); + ConnectTextureTo(sourceName, mat, "_SpecularMask", "SpecMask", + matJson, "Custom Shader/Image/Specular Mask"); + ConnectTextureTo(sourceName, mat, "_MicroNormalMap", "MicroN", matJson, "Custom Shader/Image/MicroNormal", FLAG_NORMAL); @@ -1535,6 +1603,10 @@ private void ConnectHQSkinMaterial(GameObject obj, string sourceName, Material s float specular = matJson.GetFloatValue("Custom Shader/Variable/_Specular"); float smoothnessMax = Util.CombineSpecularToSmoothness(specular, ValueByPipeline(1f, 0.9f, 1f)); mat.SetFloatIf("_SmoothnessMax", smoothnessMax); + //float secondarySmoothness = 0.85f * smoothnessMax; + //float smoothnessMix = Mathf.Clamp(0.15f * ((1f / Mathf.Pow(secondarySmoothness, 4f)) - 1f), 0.05f, 0.9f); + //mat.SetFloatIf("_Smoothness2", secondarySmoothness); + //mat.SetFloatIf("_SmoothnessMix", smoothnessMix); // URP's lights affect the AMP SSS more than 3D or HDRP mat.SetFloatIf("_SubsurfaceScale", matJson.GetFloatValue("Subsurface Scatter/Lerp")); mat.SetFloatIf("_MicroSmoothnessMod", -matJson.GetFloatValue("Custom Shader/Variable/Micro Roughness Scale")); @@ -2156,6 +2228,36 @@ private void PrepDefaultMap(string sourceName, string suffix, QuickJSON jsonData if (!DoneTexture(tex)) SetTextureImport(tex, name, FLAG_FOR_BAKE + flags); } + private void FixHDRPMap(Material sharedMat, string sourceName, QuickJSON jsonData, + string maskJsonPath = "Textures/HDRP", string maskSuffix = "HDRP", + string detailJsonPath = "Custom Shader/Image/MicroNormalMask", string detailSuffix = "MicroNMask") + { + int flags = 0; + string maskJsonTexturePath = jsonData?.GetStringValue(maskJsonPath + "/Texture Path"); + string detailJsonTexturePath = jsonData?.GetStringValue(detailJsonPath + "/Texture Path"); + + Texture2D mask = GetTextureFrom(maskJsonTexturePath, sourceName, maskSuffix, out string maskName, true); + Texture2D detail = GetTextureFrom(detailJsonTexturePath, sourceName, detailSuffix, out string detailName, true); + + // make sure to set the correct import settings for + // these textures before using them for baking... + if (!DoneTexture(mask)) SetTextureImport(mask, maskName, FLAG_FOR_BAKE + flags); + if (!DoneTexture(detail)) SetTextureImport(detail, detailName, FLAG_FOR_BAKE + flags); + + ComputeBake baker = new ComputeBake(fbx, characterInfo); + + Texture2D bakedTex = null; + + if (mask && detail) + { + bakedTex = baker.BakeCorrectedHDRPMap(mask, detail, maskName); + if (bakedTex) + { + bakedHDRPMaps.Add(sharedMat, bakedTex); + } + } + } + private void BakeDefaultMap(Material sharedMat, string sourceName, string shaderRef, string suffix, QuickJSON jsonData, string jsonPath) { ComputeBake baker = new ComputeBake(fbx, characterInfo); @@ -2175,12 +2277,18 @@ private void BakeDefaultMap(Material sharedMat, string sourceName, string shader { case "_ThicknessMap": bakedTex = baker.BakeDefaultSkinThicknessMap(tex, name); - bakedThicknessMaps.Add(sharedMat, bakedTex); + if (bakedTex) + { + bakedThicknessMaps.Add(sharedMat, bakedTex); + } break; case "_DetailMap": bakedTex = baker.BakeDefaultDetailMap(tex, name); - bakedDetailMaps.Add(sharedMat, bakedTex); + if (bakedTex) + { + bakedDetailMaps.Add(sharedMat, bakedTex); + } break; } } @@ -2543,5 +2651,22 @@ private void SetFloatPowerRange(Material mat, string shaderRef, float value, flo { mat.SetFloatIf(shaderRef, Mathf.Lerp(min, max, Mathf.Pow(value, power))); } + + public void ProcessMotionFbx(string guid, Avatar sourceAvatar, GameObject sourcePrefabAsset) + { + string motionAssetPath = AssetDatabase.GUIDToAssetPath(guid); + if (!string.IsNullOrEmpty(motionAssetPath)) + { + Util.LogInfo("Processing motion Fbx: " + motionAssetPath); + RL.DoMotionImport(characterInfo, sourceAvatar, motionAssetPath); + + // extract and retarget animations if needed. + int animationRetargeted = characterInfo.DualMaterialHair ? 2 : 1; + bool replace = characterInfo.animationRetargeted != animationRetargeted; + if (replace) Util.LogInfo("Retargeting all imported animations: " + motionAssetPath); + AnimRetargetGUI.GenerateCharacterTargetedAnimations(motionAssetPath, sourcePrefabAsset, replace); + characterInfo.animationRetargeted = animationRetargeted; + } + } } } diff --git a/Editor/ImporterMenu.cs b/Editor/ImporterMenu.cs index 33b9e38..23e27a2 100644 --- a/Editor/ImporterMenu.cs +++ b/Editor/ImporterMenu.cs @@ -137,7 +137,7 @@ public static void DoToggleOff() public static void DoScreenShot() { WindowManager.TakeScreenShot(); - } + } #if SOUPDEV @@ -369,6 +369,79 @@ public static void DoSet12() redMaskR, greenMaskR, blueMaskR, alphaMaskR, 512, "RL_WrinkleMask_Set12"); } + + + + [MenuItem("Reallusion/Dev/Proc Bump", priority = 220)] + public static void DoProcBump() + { + int width = 2048; + int height = 2048; + + Texture2D bumpMap = new Texture2D(width, height, TextureFormat.RGBA32, true); + + Color[] pixels = bumpMap.GetPixels(); + float[] values = new float[pixels.Length]; + + float[] rx = new float[100]; + float[] ry = new float[100]; + float[] rf = new float[100]; + + float scale = 1f; + + for (int i = 0; i < 100; i++) + { + rx[i] = Random.Range(0f, width); + ry[i] = Random.Range(0f, height); + rf[i] = Random.Range(0f, 1f); + } + + float maxValue = 0f; + float minValue = 0f; + + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + float map = 0.0f; + for (int i = 0; i < 100; i++) + { + float xrx = x - rx[i]; + float yry = y - ry[i]; + map += Mathf.Sin((Mathf.Sqrt(xrx * xrx + yry * yry) / (2.08f + 5.0f * rf[i])) / scale); + } + float rgb = map/100f; + values[y * width + x] = rgb; + maxValue = Mathf.Max(maxValue, rgb); + minValue = Mathf.Min(minValue, rgb); + } + } + + for (int i = 0; i < pixels.Length; i++) + { + float rgb = Mathf.InverseLerp(minValue, maxValue, values[i]); + pixels[i] = new Color(rgb, rgb, rgb, 1.0f); + } + + bumpMap.SetPixels(pixels); + + WritePNG("Assets/Temp", bumpMap, "ProcBump"); + } + + private static string WritePNG(string folderPath, Texture2D saveTexture, string textureName) + { + string filePath = Path.Combine(Path.GetDirectoryName(Application.dataPath), folderPath, textureName + ".png"); + + Util.EnsureAssetsFolderExists(folderPath); + + byte[] pngArray = saveTexture.EncodeToPNG(); + + File.WriteAllBytes(filePath, pngArray); + + string assetPath = Util.GetRelativePath(filePath); + if (File.Exists(filePath)) return assetPath; + else return ""; + } #endif } } \ No newline at end of file diff --git a/Editor/ImporterWindow.cs b/Editor/ImporterWindow.cs index 47932ba..6747f85 100644 --- a/Editor/ImporterWindow.cs +++ b/Editor/ImporterWindow.cs @@ -28,8 +28,19 @@ namespace Reallusion.Import { + [System.Serializable] public class ImporterWindow : EditorWindow { + + [SerializeField] + private static bool sceneFocus = false; + + public static bool isSceneFocus { get { return sceneFocus; } } + public static void SetSceneFocus(bool val) + { + sceneFocus = val; + } + public enum Mode { none, single, multi } private static readonly string windowTitle = "CC/iC Importer " + Pipeline.FULL_VERSION; @@ -38,8 +49,9 @@ public enum Mode { none, single, multi } private static string backScenePath; private static Mode mode; public static ImporterWindow Current { get; private set; } - public CharacterInfo Character { get { return contextCharacter; } } - + public CharacterInfo Character { get { return contextCharacter; } } + public static List ValidCharacters => validCharacters; + private Vector2 iconScrollView; private bool previewCharacterAfterGUI; private bool refreshAfterGUI; @@ -260,15 +272,18 @@ private void PreviewCharacter() { StoreBackScene(); - WindowManager.OpenPreviewScene(contextCharacter.Fbx); + PreviewScene ps = WindowManager.OpenPreviewScene(contextCharacter.Fbx); if (WindowManager.showPlayer) WindowManager.ShowAnimationPlayer(); ResetAllSceneViewCamera(); + + // lighting doesn't update correctly when first previewing a scene in HDRP + EditorApplication.delayCall += ForceUpdateLighting; } - private void RefreshCharacterList() + public void RefreshCharacterList() { if (validCharacters == null) validCharacters = new List(); @@ -378,7 +393,8 @@ private void OnGUI() RestoreData(); RestoreSelection(); - + + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); if (validCharacters == null || validCharacters.Count == 0) { GUILayout.BeginVertical(); @@ -405,7 +421,8 @@ private void OnGUI() GUILayout.FlexibleSpace(); GUILayout.EndVertical(); return; - } + } + EditorGUI.EndDisabledGroup(); float width = position.width - WINDOW_MARGIN; float height = position.height - WINDOW_MARGIN; @@ -446,8 +463,10 @@ private void OnGUI() CheckDragAndDrop(); //OnGUIIconArea(iconBlock); + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); OnGUIFlexibleIconArea(iconBlock); OnGUIDragBarArea(dragBar); + EditorGUI.EndDisabledGroup(); if (windowMode == ImporterWindowMode.Build) OnGUIInfoArea(infoBlock); @@ -553,12 +572,12 @@ private void OnGUIInfoArea(Rect infoBlock) private void OnGUIOptionArea(Rect optionBlock) { GUILayout.BeginArea(optionBlock); - + GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); - GUILayout.BeginVertical(); - + GUILayout.BeginVertical(); + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); if (contextCharacter.Generation == BaseGeneration.Unknown) { if (EditorGUILayout.DropdownButton( @@ -585,10 +604,12 @@ private void OnGUIOptionArea(Rect optionBlock) menu.AddItem(new GUIContent("High Quality Materials"), contextCharacter.HQMaterials, MaterialOptionSelected, false); menu.ShowAsContext(); } + EditorGUI.EndDisabledGroup(); GUILayout.Space(1f); - if (contextCharacter.BasicMaterials) GUI.enabled = false; + //if (contextCharacter.BasicMaterials) GUI.enabled = false; + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying || contextCharacter.BasicMaterials); if (EditorGUILayout.DropdownButton( content: new GUIContent(contextCharacter.QualEyes.ToString() + " Eyes"), focusType: FocusType.Passive)) @@ -625,17 +646,31 @@ private void OnGUIOptionArea(Rect optionBlock) 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) + { + EditorGUI.BeginChangeCheck(); contextCharacter.ShaderFlags = (CharacterInfo.ShaderFeatureFlags)EditorGUILayout.EnumFlagsField(contextCharacter.ShaderFlags); - - GUI.enabled = true; + if (EditorGUI.EndChangeCheck()) + { + if ((contextCharacter.ShaderFlags & CharacterInfo.ShaderFeatureFlags.SpringBoneHair) > 0 && + (contextCharacter.ShaderFlags & CharacterInfo.ShaderFeatureFlags.HairPhysics) > 0) + { + contextCharacter.ShaderFlags -= CharacterInfo.ShaderFeatureFlags.SpringBoneHair; + } + } + } + EditorGUI.EndDisabledGroup(); + //GUI.enabled = true; GUILayout.Space(8f); - if (contextCharacter.BuiltBasicMaterials) GUI.enabled = false; + //if (contextCharacter.BuiltBasicMaterials) GUI.enabled = false; + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying || contextCharacter.BuiltBasicMaterials); if (EditorGUILayout.DropdownButton( content: new GUIContent(contextCharacter.BakeCustomShaders ? "Bake Custom Shaders":"Bake Default Shaders"), focusType: FocusType.Passive)) @@ -658,7 +693,8 @@ private void OnGUIOptionArea(Rect optionBlock) menu.AddItem(new GUIContent("Separate Baked Prefab"), contextCharacter.BakeSeparatePrefab, BakePrefabOptionSelected, true); menu.ShowAsContext(); } - GUI.enabled = true; + EditorGUI.EndDisabledGroup(); + //GUI.enabled = true; GUILayout.Space(8f); @@ -673,11 +709,13 @@ private void OnGUIOptionArea(Rect optionBlock) GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); if (GUILayout.Button(buildContent, GUILayout.Height(BUTTON_HEIGHT), GUILayout.Width(160f))) { buildAfterGUI = true; } + EditorGUI.EndDisabledGroup(); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); @@ -686,7 +724,7 @@ private void OnGUIOptionArea(Rect optionBlock) GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); - + EditorGUI.EndDisabledGroup(); GUILayout.EndArea(); } @@ -699,6 +737,7 @@ private void OnGUIActionArea(Rect actionBlock) GUILayout.BeginVertical(); + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); if (false && !string.IsNullOrEmpty(backScenePath) && File.Exists(backScenePath)) { if (GUILayout.Button(new GUIContent("<", "Go back to the last valid scene."), @@ -729,73 +768,93 @@ private void OnGUIActionArea(Rect actionBlock) GUILayout.Space(ACTION_BUTTON_SPACE); } + EditorGUI.EndDisabledGroup(); GUILayout.Space(ACTION_BUTTON_SPACE + 11f); - if (contextCharacter.BuiltBasicMaterials) GUI.enabled = false; + //if (contextCharacter.BuiltBasicMaterials) GUI.enabled = false; + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying || contextCharacter.BuiltBasicMaterials); if (GUILayout.Button(new GUIContent(contextCharacter.bakeIsBaked ? iconActionBakeOn : iconActionBake, "Bake high quality materials down to compatible textures for the default shaders. i.e. HDRP/Lit, URP/Lut or Standard shader."), GUILayout.Width(ACTION_BUTTON_SIZE), GUILayout.Height(ACTION_BUTTON_SIZE))) { bakeAfterGUI = true; } - GUI.enabled = true; + EditorGUI.EndDisabledGroup(); + //GUI.enabled = true; GUILayout.Space(ACTION_BUTTON_SPACE); if (contextCharacter.tempHairBake) { + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); if (GUILayout.Button(new GUIContent(iconActionBakeHairOn, "Restore original hair diffuse textures."), GUILayout.Width(ACTION_BUTTON_SIZE), GUILayout.Height(ACTION_BUTTON_SIZE))) { restoreHairAfterGUI = true; } + EditorGUI.EndDisabledGroup(); } else //if (!contextCharacter.BuiltBasicMaterials && contextCharacter.HasColorEnabledHair()) { - if (contextCharacter.BuiltBasicMaterials || !contextCharacter.HasColorEnabledHair()) GUI.enabled = false; - + //if (contextCharacter.BuiltBasicMaterials || !contextCharacter.HasColorEnabledHair()) GUI.enabled = false; + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying || contextCharacter.BuiltBasicMaterials || !contextCharacter.HasColorEnabledHair()); if (GUILayout.Button(new GUIContent(iconActionBakeHair, "Bake hair diffuse textures, to preview the baked results of the 'Enable Color' in the hair materials."), GUILayout.Width(ACTION_BUTTON_SIZE), GUILayout.Height(ACTION_BUTTON_SIZE))) { bakeHairAfterGUI = true; } + EditorGUI.EndDisabledGroup(); } - GUI.enabled = true; + //GUI.enabled = true; GUILayout.Space(ACTION_BUTTON_SPACE); - if (contextCharacter.Unprocessed) GUI.enabled = false; + //if (contextCharacter.Unprocessed) GUI.enabled = false; + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying || contextCharacter.Unprocessed); if (GUILayout.Button(new GUIContent(iconActionAnims, "Process, extract and rename character animations and create a default animtor controller."), GUILayout.Width(ACTION_BUTTON_SIZE), GUILayout.Height(ACTION_BUTTON_SIZE))) { - RL.SetAnimationImport(contextCharacter, contextCharacter.Fbx); - AnimRetargetGUI.GenerateCharacterTargetedAnimations(contextCharacter.Fbx, null, true); + RL.DoAnimationImport(contextCharacter, contextCharacter.Fbx); + AnimRetargetGUI.GenerateCharacterTargetedAnimations(contextCharacter.path, contextCharacter.Fbx, true); + List motionGuids = contextCharacter.GetMotionGuids(); + if (motionGuids.Count > 0) + { + Avatar sourceAvatar = contextCharacter.GetCharacterAvatar(); + foreach (string motionGuid in motionGuids) + { + string motionPath = AssetDatabase.GUIDToAssetPath(motionGuid); + AnimRetargetGUI.GenerateCharacterTargetedAnimations(motionPath, contextCharacter.Fbx, true); + } + } int animationRetargeted = contextCharacter.DualMaterialHair ? 2 : 1; contextCharacter.animationRetargeted = animationRetargeted; contextCharacter.Write(); } - + EditorGUI.EndDisabledGroup(); // GUILayout.Space(ACTION_BUTTON_SPACE); - + + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); if (GUILayout.Button(new GUIContent(iconActionPhysics, "Rebuilds the character physics."), GUILayout.Width(ACTION_BUTTON_SIZE), GUILayout.Height(ACTION_BUTTON_SIZE))) { physicsAfterGUI = true; } - GUI.enabled = true; + EditorGUI.EndDisabledGroup(); + //GUI.enabled = true; #if UNITY_ALEMBIC_1_0_7 GUILayout.Space(ACTION_BUTTON_SPACE); - + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); if (GUILayout.Button(new GUIContent(iconAlembic, "Process alembic animations with this character's materials."), GUILayout.Width(ACTION_BUTTON_SIZE), GUILayout.Height(ACTION_BUTTON_SIZE))) { Alembic.ProcessAlembics(contextCharacter.Fbx, contextCharacter.name, contextCharacter.folder); } - GUI.enabled = true; + EditorGUI.EndDisabledGroup(); + //GUI.enabled = true; #endif /* @@ -818,6 +877,7 @@ private void OnGUIActionArea(Rect actionBlock) GUILayout.Space(ACTION_BUTTON_SPACE); + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); if (GUILayout.Button(new GUIContent(iconActionLOD, "Run the LOD combining tool on the prefabs associated with this character."), GUILayout.Width(ACTION_BUTTON_SIZE), GUILayout.Height(ACTION_BUTTON_SIZE))) { @@ -825,11 +885,13 @@ private void OnGUIActionArea(Rect actionBlock) Selection.activeObject = AssetDatabase.LoadAssetAtPath(prefabsFolder, typeof(Object)) as Object; LodSelectionWindow.InitTool(); } - GUI.enabled = true; + EditorGUI.EndDisabledGroup(); + //GUI.enabled = true; GUILayout.Space(ACTION_BUTTON_SPACE * 2f + 11f); - if (contextCharacter == null) GUI.enabled = false; + //if (contextCharacter == null) GUI.enabled = false; + EditorGUI.BeginDisabledGroup(contextCharacter == null); if (GUILayout.Button(new GUIContent(AnimPlayerGUI.IsPlayerShown() ? iconActionAnimPlayerOn : iconActionAnimPlayer, "Show animation preview player."), GUILayout.Width(ACTION_BUTTON_SIZE), GUILayout.Height(ACTION_BUTTON_SIZE))) { @@ -846,11 +908,13 @@ private void OnGUIActionArea(Rect actionBlock) ResetAllSceneViewCamera(characterPrefab); } } - GUI.enabled = true; - + EditorGUI.EndDisabledGroup(); + //GUI.enabled = true; + GUILayout.Space(ACTION_BUTTON_SPACE); - if (contextCharacter == null) GUI.enabled = false; + //if (contextCharacter == null) GUI.enabled = false; + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying || contextCharacter == null); if (GUILayout.Button(new GUIContent(AnimRetargetGUI.IsPlayerShown() ? iconActionAvatarAlignOn : iconActionAvatarAlign, "Animation Adjustment & Retargeting."), GUILayout.Width(ACTION_BUTTON_SIZE), GUILayout.Height(ACTION_BUTTON_SIZE))) { @@ -864,7 +928,8 @@ private void OnGUIActionArea(Rect actionBlock) WindowManager.ShowAnimationRetargeter(); } } - GUI.enabled = true; + //GUI.enabled = true; + EditorGUI.EndDisabledGroup(); GUILayout.FlexibleSpace(); @@ -897,7 +962,7 @@ private void OnGUIActionArea(Rect actionBlock) GUILayout.Space(ACTION_BUTTON_SPACE); - + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); GUIContent settingsIconGC; if (windowMode != ImporterWindowMode.Settings) settingsIconGC = new GUIContent(iconSettings, "Settings."); @@ -911,6 +976,7 @@ private void OnGUIActionArea(Rect actionBlock) else windowMode = ImporterWindowMode.Build; } + EditorGUI.EndDisabledGroup(); GUILayout.EndVertical(); @@ -950,7 +1016,14 @@ private void OnGUITreeViewArea(Rect treeviewBlock) private void OnGUISettingsArea(Rect settingsBlock) { + if (EditorApplication.isPlaying) + { + windowMode = ImporterWindowMode.Build; + return; + } + GUILayout.BeginArea(settingsBlock); + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); @@ -964,7 +1037,14 @@ private void OnGUISettingsArea(Rect settingsBlock) GUILayout.EndHorizontal(); GUILayout.Space(TITLE_SPACE); - if (!Pipeline.isHDRP) + if (Pipeline.isHDRP) + { + Importer.USE_DIGITAL_HUMAN_SHADER = GUILayout.Toggle(Importer.USE_DIGITAL_HUMAN_SHADER, + new GUIContent("Use Dual Specular Shaders", "Use Dual Specular shaders where possible. Dual specular shaders use the stack lit master node which is forward only. "+ + "The dual specular shader setups are based principles used in the Heretic digital human shaders.")); + GUILayout.Space(ROW_SPACE); + } + else { Importer.USE_AMPLIFY_SHADER = GUILayout.Toggle(Importer.USE_AMPLIFY_SHADER, new GUIContent("Use Amplify Shaders", "Use the more advanced Amplify shaders where possible. " + @@ -994,6 +1074,10 @@ private void OnGUISettingsArea(Rect settingsBlock) new GUIContent("Animation Player On", "Always show the animation player when opening the preview scene.")); GUILayout.Space(ROW_SPACE); + Importer.USE_SELF_COLLISION = GUILayout.Toggle(Importer.USE_SELF_COLLISION, + new GUIContent("Use self collision", "Use the self collision distances from the Character Creator export.")); + GUILayout.Space(ROW_SPACE); + GUILayout.Space(10f); GUILayout.BeginVertical(new GUIContent("", "Override mip-map bias for all textures setup for the characters."), importerStyles.labelStyle); GUILayout.Label("Mip-map Bias"); @@ -1018,6 +1102,7 @@ private void OnGUISettingsArea(Rect settingsBlock) GUILayout.EndVertical(); GUILayout.Space(ROW_SPACE); + /* GUILayout.Space(10f); GUILayout.BeginVertical(new GUIContent("", "When assigning weight maps, the system analyses the weights of the mesh to determine which colliders affect the cloth simulation.Only cloth weights above this threshold will be considered for collider detection. Note: This is the default value supplied to the WeightMapper component, it can be further modified there."), importerStyles.labelStyle); GUILayout.Label("Collider Detection Threshold"); @@ -1029,6 +1114,7 @@ private void OnGUISettingsArea(Rect settingsBlock) GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUILayout.Space(ROW_SPACE); + */ GUILayout.Space(10f); string label = "Log Everything"; @@ -1064,6 +1150,7 @@ private void OnGUISettingsArea(Rect settingsBlock) GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); + EditorGUI.EndDisabledGroup(); GUILayout.EndArea(); } @@ -1343,13 +1430,20 @@ public static void ResetAllSceneViewCamera(GameObject targetOverride = null) } } + public static void ForceUpdateLighting() + { + PreviewScene.PokeLighting(); + } + public static void ResetOptions() { Importer.MIPMAP_BIAS = 0f; Importer.RECONSTRUCT_FLOW_NORMALS = false; Importer.REBAKE_BLENDER_UNITY_MAPS = false; Importer.ANIMPLAYER_ON_BY_DEFAULT = false; + Importer.USE_SELF_COLLISION = false; Importer.USE_AMPLIFY_SHADER = true; + Importer.USE_DIGITAL_HUMAN_SHADER = false; Physics.PHYSICS_SHRINK_COLLIDER_RADIUS = 0.5f; Physics.PHYSICS_WEIGHT_MAP_DETECT_COLLIDER_THRESHOLD = 0.25f; Util.LOG_LEVEL = 0; diff --git a/Editor/MassProcessingWindow.cs b/Editor/MassProcessingWindow.cs new file mode 100644 index 0000000..a394790 --- /dev/null +++ b/Editor/MassProcessingWindow.cs @@ -0,0 +1,1339 @@ +/* + * Copyright (C) 2021 Victor Soupday + * This file is part of CC_Unity_Tools + * + * CC_Unity_Tools is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CC_Unity_Tools is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CC_Unity_Tools. If not, see . + */ + +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using System; +using System.Linq; +using System.IO; +using UnityEditor.PackageManager.UI; +using JetBrains.Annotations; + +namespace Reallusion.Import +{ + public class MassProcessingWindow : EditorWindow + { + enum WindowMode { standard, extended } + public enum DragBarId { a, b } + public enum SortType { ascending, descending } + public enum FilterType { all, processed, unprocessed } + private static WindowMode windowMode; + public static SortType windowSortType; + public static FilterType windowFilterType; + + const float WINDOW_MARGIN = 2f; + const float TOP_PADDING = 2f; + const float SETTINGS_TOP_PADDING = 20f; + const float INITIAL_PROC_LIST_WIDTH = 360f; + const float INITIAL_PROC_FLAGS_WIDTH = 60f; + const float PROC_LIST_MIN_W = 180f; + const float PROC_FLAGS_MIN_W = 30f; + const float PROC_CTRL_HEIGHT = 48f; + const float SEARCH_BAR_HEIGHT = 20f; + const float INITIAL_PROC_LIST_HEIGHT = 480f;//500f; + const float PROC_LIST_MIN_H = 250f; + const float INITIAL_SETTINGS_WIDTH = 220f; + const float SETTINGS_MIN_W = 75f; + const float DRAG_BAR_WIDTH = 2f; + const float DRAG_HANDLE_PADDING = 4f; + const float ICON_SIZE = 64f; + const float ICON_WIDTH_DETAIL = 140f; + const float ICON_SIZE_SMALL = 25f; + const float ICON_SIZE_MID = 42f; + const float ICON_DETAIL_MARGIN = 2f; + const float LIST_MEMBER_LEFT_MARGIN = 6f; + private float PROC_LIST_WIDTH = INITIAL_PROC_LIST_WIDTH; + private float PROC_FLAGS_WIDTH = INITIAL_PROC_FLAGS_WIDTH; + private float SETTINGS_WIDTH = INITIAL_SETTINGS_WIDTH; + private bool dragging = false; + private bool repaintDelegated = false; + private Vector2 listScrollPosition; + private Styles windowStyles; + + private Texture2D iconUnprocessed; + private Texture2D iconBasic; + private Texture2D iconHQ; + private Texture2D iconBaked; + private Texture2D iconMixed; + private Texture2D iconListUnchecked; + private Texture2D iconListChecked; + private Texture2D iconListRemove; + private Texture2D iconSettingsShown; + private Texture2D iconSettings; + private Texture2D iconSettingsShownChanged; + private Texture2D iconSettingsChanged; + private Texture2D iconSortList; + private Texture2D iconStartProcessing; + private Texture2D iconFilterEdit; + private Texture2D iconFilterRemove; + private Texture2D iconRefreshList; + + private bool initDone = false; + private ImporterWindow importerWindow; + List workingList; + List displayList; + CharacterInfo characterSettings; + private bool isMassSelected = false; + private string searchString = string.Empty; + List buildQueue; + + [MenuItem("Reallusion/Processing Tools/Batch Processing", priority = 400)] + public static void ATInitAssetProcessing() + { + OpenProcessingWindow(); + } + + [MenuItem("Reallusion/Processing Tools/Batch Processing", true)] + public static bool ValidateATInitAssetProcessing() + { + if (ImporterWindow.Current != null && !EditorWindow.HasOpenInstances()) + return true; + else + return false; + } + + public static MassProcessingWindow OpenProcessingWindow() + { + //window = EditorWindow.GetWindow(); + MassProcessingWindow window = ScriptableObject.CreateInstance(); + bool windowAsUtility = true; + windowMode = WindowMode.standard; + windowSortType = SortType.ascending; + + if (windowAsUtility) + window.ShowUtility(); // non dockable window + else + window.Show(); // dockable window + + window.minSize = GetMinSize(); + //initial window dimensions + Rect centerPosition = GetRectToCenterWindow(INITIAL_PROC_LIST_WIDTH, INITIAL_PROC_LIST_HEIGHT); + window.position = centerPosition; + window.titleContent = new GUIContent("CC/iC Importer - Batch Processing"); + return window; + } + + private void InitData() + { + SetWindowSize(); + importerWindow = ImporterWindow.Current; + + string[] folders = new string[] { "Assets", "Packages" }; + iconUnprocessed = Util.FindTexture(folders, "RLIcon_UnprocessedChar"); + iconBasic = Util.FindTexture(folders, "RLIcon_BasicChar"); + iconHQ = Util.FindTexture(folders, "RLIcon_HQChar"); + iconBaked = Util.FindTexture(folders, "RLIcon_BakedChar"); + iconMixed = Util.FindTexture(folders, "RLIcon_MixedChar"); + iconListUnchecked = Util.FindTexture(folders, "RLIcon_ListUnchecked"); + iconListChecked = Util.FindTexture(folders, "RLIcon_ListChecked"); + iconListRemove = Util.FindTexture(folders, "RLIcon_ListRemove"); + iconSettingsShown = Util.FindTexture(folders, "RLIcon_BuildSettingsShown"); + iconSettings = Util.FindTexture(folders, "RLIcon_BuildSettings"); + iconSettingsShownChanged = Util.FindTexture(folders, "RLIcon_BuildSettingsShown_changed"); + iconSettingsChanged = Util.FindTexture(folders, "RLIcon_BuildSettings_changed"); + iconSortList = Util.FindTexture(folders, "RLIcon_SortList"); + iconRefreshList = Util.FindTexture(folders, "RLIcon_RefreshList"); + iconFilterEdit = Util.FindTexture(folders, "RLIcon_FilterEdit"); + iconFilterRemove = Util.FindTexture(folders, "RLIcon_FilterRemove"); + iconStartProcessing = Util.FindTexture(folders, "RLIcon_StartProcessing"); + initDone = true; + } + + private float batchTimer = 0f; + public void BatchUpdateTimer() + { + if (batchTimer > 0f) + { + if (Time.realtimeSinceStartup > batchTimer) + { + batchTimer = 0f; + EditorApplication.update -= BatchUpdateTimer; + BatchBuildNextQueueCharacter(); + } + } + else + { + batchTimer = 0f; + EditorApplication.update -= BatchUpdateTimer; + } + } + + public void BatchQueueNextBuild(float delay) + { + EditorApplication.update -= BatchUpdateTimer; + Selection.activeObject = null; + + if (buildQueue == null || buildQueue.Count == 0 || ImporterWindow.Current == null) + { + Util.LogInfo("Done batch processing!"); + batchTimer = 0f; + // reset the window and displayed list at the end of the build process + ResetSettings(); + } + else + { + Util.LogInfo("Building: " + buildQueue[0].name + " (" + buildQueue.Count + " remaining) in " + delay + "s"); + batchTimer = Time.realtimeSinceStartup + delay; + EditorApplication.update += BatchUpdateTimer; + } + } + + public void BatchBuildNextQueueCharacter() + { + if (ImporterWindow.Current == null) + { + buildQueue = null; + return; + } + + if (buildQueue == null || buildQueue.Count == 0) return; + + CharacterInfo batchCharacter = buildQueue[0]; + + CharacterInfo character = ImporterWindow.ValidCharacters.Where(t => t.guid == batchCharacter.guid).FirstOrDefault(); + if (character != null) + { + Util.LogInfo("Batch Queue Processing: " + character.name); + + character.CopySettings(batchCharacter); + + // default to high quality if never set before + if (character.BuildQuality == MaterialQuality.None) + character.BuildQuality = MaterialQuality.High; + + // refresh the character info for any Json changes + character.Refresh(); + + // import and build the materials from the Json data + Importer import = new Importer(character); + GameObject prefab = import.Import(true); + character.Write(); + character.Release(); + } + + buildQueue.Remove(batchCharacter); + + BatchQueueNextBuild(1f); + + EditorApplication.delayCall += ProcessingRefresh; + } + + private void ProcessingRefresh() + { + importerWindow.RefreshCharacterList(); + FilterDisplayedList(); + } + + private void ResetSettings() + { + ResetWindow(); + workingList = BuildCharacterInfoList(); + windowSortType = SortType.ascending; + windowFilterType = FilterType.all; + isMassSelected = false; + searchString = string.Empty; + GUI.FocusControl(""); + FilterDisplayedList(); + } + + private bool IsInFilteredDisplayList(CharacterInfo character) + { + foreach (CharacterListDisplay cld in displayList) + { + if (cld.guid == character.guid) return true; + } + + return false; + } + + public void BeginMassProcessing() + { + // add a delayed call to refresh the char list in the importer window and the batch window + EditorApplication.delayCall += ProcessingRefresh; + buildQueue = new List(); + + foreach (CharacterInfo character in workingList) + { + // all individual settings are stored in CharacterInfoList character (base class CharacterInfo) + if (character.selectedInList && IsInFilteredDisplayList(character)) + { + // process character. + if (!buildQueue.Contains(character)) + { + character.FixCharSettings(); + buildQueue.Add(character); + } + } + } + + BatchQueueNextBuild(1f); + } + + public static Vector2 GetMinSize() + { + float minWidth = PROC_LIST_MIN_W; + if (windowMode == WindowMode.extended) minWidth += SETTINGS_MIN_W; + + return new Vector2(minWidth, PROC_LIST_MIN_H); + } + + public void SetWindowSize() + { + Rect pos = this.position; + switch (windowMode) + { + case WindowMode.standard: { pos.width = PROC_LIST_WIDTH + WINDOW_MARGIN; break; } + case WindowMode.extended: { pos.width = PROC_LIST_WIDTH + DRAG_BAR_WIDTH + SETTINGS_WIDTH + WINDOW_MARGIN; break; } + } + this.position = pos; + Repaint(); + } + + public class CharacterListDisplay + { + public string guid; + public string displayName; + public bool selectedInList; + public bool settingsChanged; + public bool bakeIsBaked; + public bool BuiltBasicMaterials; + public bool BuiltHQMaterials; + + public CharacterListDisplay(string guidString, List masterList) + { + CharacterInfo info = masterList.Where(t => t.guid == guidString).FirstOrDefault(); + if (info != null) + { + guid = guidString; + displayName = info.name; + selectedInList = info.selectedInList; + settingsChanged = info.settingsChanged; + bakeIsBaked = info.bakeIsBaked; + BuiltBasicMaterials = info.BuiltBasicMaterials; + BuiltHQMaterials = info.BuiltHQMaterials; + } + else + { + guid = "0000"; + displayName = "Cannot Display Name"; + selectedInList = false; + settingsChanged = false; + bakeIsBaked = false; + BuiltBasicMaterials = false; + BuiltHQMaterials = false; + } + } + } + + public List BuildCharacterInfoList() + { + List output = new List(); + + if (ImporterWindow.ValidCharacters?.Count > 0) + { + foreach (Reallusion.Import.CharacterInfo c in ImporterWindow.ValidCharacters) + { + output.Add(new CharacterInfo(c.guid)); + } + } + + return output; + } + + public List BuildCharacterListDisplay(List masterList, SortType sortType, FilterType filterType) + { + List output = new List(); + + if (masterList != null && masterList.Count > 0) + { + List processingList = masterList.ToList(); + + IEnumerable query = new List(); + + switch (sortType) + { + case SortType.ascending: + { + query = from character in processingList + orderby character.name ascending //.Substring(0, 1) ascending + select character; + break; + } + case SortType.descending: + { + query = from character in processingList + orderby character.name descending //.Substring(0, 1) descending + select character; + break; + } + } + + foreach (CharacterInfo c in query) + { + bool searchMatch = false; + + if (string.IsNullOrEmpty(searchString)) + { + searchMatch = true; + } + else + { + if (!string.IsNullOrEmpty(c.name)) + { + bool contains = c.name.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0; + if (contains) + { + searchMatch = true; + } + } + } + + CharacterListDisplay newInfo = new CharacterListDisplay(c.guid, masterList); + CharacterInfo originalInfo = (CharacterInfo)workingList.Where(t => t.guid.Equals(c.guid)).FirstOrDefault(); + if (originalInfo != null) + { + newInfo.selectedInList = originalInfo.selectedInList; + } + + switch (filterType) + { + case FilterType.all: + { + if (searchMatch) + output.Add(newInfo); + break; + } + case FilterType.processed: + { + if (searchMatch) + { + if (newInfo.BuiltBasicMaterials || newInfo.BuiltHQMaterials) output.Add(newInfo); + } + break; + + } + case FilterType.unprocessed: + { + if (searchMatch) + { + if (!newInfo.BuiltBasicMaterials && !newInfo.BuiltHQMaterials) output.Add(newInfo); + } + break; + } + } + } + } + + return output; + } + + public void FilterDisplayedList() + { + displayList = BuildCharacterListDisplay(workingList, windowSortType, windowFilterType); + Repaint(); + } + + public List SortAndFilterCharacterInfoList(SortType sortType, FilterType filterType) + { + List output = new List(); + + if (ImporterWindow.ValidCharacters.Count > 0) + { + List processingList = ImporterWindow.ValidCharacters.ToList(); + + IEnumerable query = new List(); + + switch (sortType) + { + case SortType.ascending: + query = from character in processingList + orderby character.name.Substring(0, 1) ascending + select character; + break; + case SortType.descending: + default: + query = from character in processingList + orderby character.name.Substring(0, 1) descending + select character; + break; + } + + foreach (Reallusion.Import.CharacterInfo c in query) + { + CharacterInfo newInfo = new CharacterInfo(c.guid); + CharacterInfo originalInfo = (CharacterInfo)workingList.Where(t => t.guid.Equals(c.guid)).FirstOrDefault(); + if (originalInfo != null) + { + newInfo.selectedInList = originalInfo.selectedInList; + } + + switch (filterType) + { + case FilterType.processed: + if (newInfo.BuiltBasicMaterials || newInfo.BuiltHQMaterials) output.Add(newInfo); + break; + case FilterType.unprocessed: + if (!newInfo.BuiltBasicMaterials && !newInfo.BuiltHQMaterials) output.Add(newInfo); + break; + case FilterType.all: + default: + output.Add(newInfo); + break; + } + } + } + return output; + } + + private void OnGUI() + { + if (ImporterWindow.Current == null) + { + CloseWindow(); + return; + } + if (!initDone) InitData(); + if (windowStyles == null) windowStyles = new Styles(); + if (workingList == null) workingList = BuildCharacterInfoList(); + if (displayList == null) FilterDisplayedList(); + + float width = position.width - WINDOW_MARGIN; + float height = position.height - WINDOW_MARGIN; + float innerHeight = height - TOP_PADDING; + float listHeight = height - TOP_PADDING * 2 - PROC_CTRL_HEIGHT - SEARCH_BAR_HEIGHT; + + if (windowMode == WindowMode.standard) PROC_LIST_WIDTH = width; + + Rect controlsRect = new Rect(0f, TOP_PADDING, PROC_LIST_WIDTH + WINDOW_MARGIN, PROC_CTRL_HEIGHT); + Rect nameFilterRect = new Rect(0f, controlsRect.yMax, PROC_LIST_WIDTH + WINDOW_MARGIN, SEARCH_BAR_HEIGHT); + Rect listRect = new Rect(0f, nameFilterRect.yMax + TOP_PADDING, PROC_LIST_WIDTH + WINDOW_MARGIN, listHeight); + Rect extendedDragBarRect = new Rect(listRect.xMax, TOP_PADDING, DRAG_BAR_WIDTH, innerHeight); + Rect extendedSettingsRect = new Rect(extendedDragBarRect.xMax, TOP_PADDING, width - PROC_LIST_WIDTH, innerHeight); + + if (windowMode == WindowMode.extended) SETTINGS_WIDTH = width - PROC_LIST_WIDTH - DRAG_BAR_WIDTH; + + //TestShowArea(controlsRect, Color.red); + //TestShowArea(listRect, Color.magenta); + + EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying); + ONGUIControlsArea(controlsRect); + OnGUINameFlterArea(nameFilterRect); + OnGUIDetailWorkingDisplayListArea(listRect); + EditorGUI.EndDisabledGroup(); + + if (windowMode == WindowMode.extended) + { + OnGUIGeneralDragBarArea(extendedDragBarRect); + OnGUIExtendedSettingsArea(extendedSettingsRect); + } + } + + private void TestShowArea(Rect area, Color color) + { + GUIStyle gUIStyle = GUI.skin.box; + gUIStyle.normal.background = TextureColor(color); + GUILayout.BeginArea(area, gUIStyle); + GUILayout.EndArea(); + } + + private void ONGUIControlsArea(Rect controlsRect) + { + GUILayout.BeginArea(controlsRect, GUI.skin.box); + GUILayout.BeginHorizontal(); + string buttonText = windowSortType == SortType.ascending ? "Sort Descending" : "Sort Ascending"; + + if (GUILayout.Button(new GUIContent(iconSortList, buttonText), GUILayout.Width(ICON_SIZE_MID), + GUILayout.Height(ICON_SIZE_MID))) + { + SortType sorting; + if (windowSortType == SortType.ascending) + sorting = SortType.descending; + else + sorting = SortType.ascending; + + windowSortType = sorting; + FilterDisplayedList(); + } + + if (GUILayout.Button(new GUIContent(iconFilterEdit, "Change List Filter"), GUILayout.Width(ICON_SIZE_MID), + GUILayout.Height(ICON_SIZE_MID))) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Display All"), windowFilterType == FilterType.all, FilterOptionSelected, FilterType.all); + menu.AddItem(new GUIContent("Show only unprocessed"), windowFilterType == FilterType.unprocessed, FilterOptionSelected, FilterType.unprocessed); + menu.AddItem(new GUIContent("Show only processed"), windowFilterType == FilterType.processed, FilterOptionSelected, FilterType.processed); + menu.ShowAsContext(); + } + + if (GUILayout.Button(new GUIContent(iconFilterRemove, "Remove all filters"), GUILayout.Width(ICON_SIZE_MID), + GUILayout.Height(ICON_SIZE_MID))) + { + windowSortType = SortType.ascending; + windowFilterType = FilterType.all; + FilterDisplayedList(); + } + + if (GUILayout.Button(new GUIContent(iconRefreshList, "Reset list and all settings."), + GUILayout.Width(ICON_SIZE_MID), + GUILayout.Height(ICON_SIZE_MID))) + { + ResetSettings(); + /* + ResetWindow(); + workingList = BuildCharacterInfoList(); + windowSortType = SortType.ascending; + windowFilterType = FilterType.all; + isMassSelected = false; + searchString = string.Empty; + GUI.FocusControl(""); + FilterDisplayedList(); + */ + } + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button(new GUIContent(iconStartProcessing, "Begin mass processing of selected items."), + GUILayout.Width(ICON_SIZE_MID), + GUILayout.Height(ICON_SIZE_MID))) + { + ResetWindow(); + BeginMassProcessing(); + } + + GUILayout.EndHorizontal(); + GUILayout.EndArea(); + } + + private void OnGUINameFlterArea(Rect areaRect) + { + Rect selectAllBoxRect = new Rect(areaRect.x, areaRect.y, 20f, areaRect.height); + Rect nameFilterRect = new Rect(selectAllBoxRect.xMax, areaRect.y, areaRect.width - selectAllBoxRect.width, areaRect.height); + Texture2D massSelectedIcon = isMassSelected ? iconListChecked : iconListUnchecked; + + GUILayout.BeginArea(selectAllBoxRect); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + + GUILayout.BeginHorizontal(); + GUILayout.Space(LIST_MEMBER_LEFT_MARGIN); + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + GUILayout.Box(massSelectedIcon, new GUIStyle(), + GUILayout.Width(16f), + GUILayout.Height(16f)); + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.EndArea(); + + if (HandleListClick(selectAllBoxRect)) + { + isMassSelected = !isMassSelected; + ToggleSelectAllDisplayed(); + Repaint(); + } + + GUILayout.BeginArea(nameFilterRect); + + GUILayout.BeginVertical(); + GUILayout.FlexibleSpace(); + //GUILayout.Space(1f); + + GUILayout.BeginHorizontal(); // horizontal container for image and label + GUILayout.Space(10f); + EditorGUI.BeginChangeCheck(); + searchString = EditorGUILayout.TextField(searchString, EditorStyles.toolbarSearchField); + + if (EditorGUI.EndChangeCheck()) + { + FilterDisplayedList(); + } + + // The TextField does not update until it loses focus, so clearing the string wont clear the text field + if (GUILayout.Button(EditorGUIUtility.IconContent("winbtn_win_close_h"), EditorStyles.toolbarButton, GUILayout.Width(22))) + { + searchString = string.Empty; + GUI.FocusControl(""); + FilterDisplayedList(); + } + + GUILayout.EndHorizontal(); + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.EndArea(); + } + + private void OnGUIDetailWorkingDisplayListArea(Rect iconBlock) + { + windowStyles.FixTexturesOnDomainReload(); + float rowHeight = ICON_SIZE_SMALL + 2 * ICON_DETAIL_MARGIN; + + Rect boxRect = new Rect(0f, 0f, PROC_LIST_WIDTH - PROC_FLAGS_WIDTH - 4f, rowHeight); + Rect flagsBoxRect = new Rect(boxRect.xMax + 2f, 0f, PROC_FLAGS_WIDTH, rowHeight); + Rect posRect = new Rect(iconBlock); + Rect viewRect = new Rect(0f, 0f, PROC_LIST_WIDTH - 14f, rowHeight * (displayList.Count + 0.5f)); + + listScrollPosition = GUI.BeginScrollView(posRect, listScrollPosition, viewRect, false, false); + for (int idx = 0; idx < displayList.Count; idx++) + { + CharacterListDisplay info = displayList[idx]; + CharacterInfo importerWindowInfo = ImporterWindow.ValidCharacters.Where(t => t.guid == info.guid).FirstOrDefault(); + Texture2D iconTexture = iconUnprocessed; + string name = ""; + if (importerWindowInfo != null) + { + name = Path.GetFileNameWithoutExtension(AssetDatabase.GUIDToAssetPath(importerWindowInfo.guid)); + if (importerWindowInfo.bakeIsBaked) + { + if (importerWindowInfo.BuiltBasicMaterials) iconTexture = iconMixed; + else if (importerWindowInfo.BuiltHQMaterials) iconTexture = iconBaked; + } + else + { + if (importerWindowInfo.BuiltBasicMaterials) iconTexture = iconBasic; + else if (importerWindowInfo.BuiltHQMaterials) iconTexture = iconHQ; + } + } + else + { + name = Path.GetFileNameWithoutExtension(AssetDatabase.GUIDToAssetPath(info.guid)); + } + + float heightDelta = ICON_SIZE_SMALL + 2 * ICON_DETAIL_MARGIN; + boxRect.y = idx * heightDelta; + flagsBoxRect.y = idx * heightDelta; + GUILayout.BeginArea(boxRect); + + GUILayout.BeginVertical(idx % 2 == 0 ? windowStyles.fakeButtonContext : windowStyles.fakeButton); + GUILayout.FlexibleSpace(); + + GUILayout.BeginHorizontal(); // horizontal container for image and label + GUILayout.Space(LIST_MEMBER_LEFT_MARGIN); + GUILayout.BeginVertical(); // vertical container for checkbox + GUILayout.FlexibleSpace(); + GUILayout.Box(info.selectedInList ? iconListChecked : iconListUnchecked, new GUIStyle(), + GUILayout.Width(16f), + GUILayout.Height(16f)); + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); + + GUILayout.BeginVertical(); // vertical container for image + GUILayout.FlexibleSpace(); + + GUILayout.Box(iconTexture, new GUIStyle(), + GUILayout.Width(ICON_SIZE_SMALL), + GUILayout.Height(ICON_SIZE_SMALL)); + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); // vertical container for image + + GUILayout.BeginVertical(); // vertical container for label + GUILayout.FlexibleSpace(); + GUILayout.Label(name, windowStyles.nameTextStyle); + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); // vertical container for label + + GUILayout.FlexibleSpace(); // fill horizontal for overall left-justify + + GUILayout.EndHorizontal(); // horizontal container for image and label + + GUILayout.FlexibleSpace(); + GUILayout.EndVertical(); //(fakeButton) + + GUILayout.EndArea(); + + if (HandleListClick(boxRect)) + { + info.selectedInList = !info.selectedInList; + CharacterInfo workingChar = workingList.Where(t => t.guid == info.guid).FirstOrDefault(); + if (workingChar != null) + { + workingChar.selectedInList = info.selectedInList; + } + Repaint(); + } + + GUILayout.BeginArea(flagsBoxRect); + + GUILayout.BeginVertical(idx % 2 == 0 ? windowStyles.fakeButtonContext : windowStyles.fakeButton); + + GUILayout.FlexibleSpace(); + + GUILayout.BeginHorizontal(); + + GUILayout.FlexibleSpace(); + + Texture2D displayIcon; + if (characterSettings == null) + { + displayIcon = info.settingsChanged == true ? iconSettingsChanged : iconSettings; + } + + else + { + displayIcon = info.guid == characterSettings.guid ? info.settingsChanged == true ? iconSettingsShownChanged : iconSettingsShown : info.settingsChanged == true ? iconSettingsChanged : iconSettings; + } + GUILayout.Box(displayIcon, new GUIStyle(), + GUILayout.Width(ICON_SIZE_SMALL), + GUILayout.Height(ICON_SIZE_SMALL)); + + GUILayout.FlexibleSpace(); + + GUILayout.EndHorizontal(); + + GUILayout.FlexibleSpace(); + + GUILayout.EndVertical(); + + GUILayout.EndArea(); + if (HandleListClick(flagsBoxRect)) + { + if (characterSettings != null) + { + if (info.guid == characterSettings.guid) + { + windowMode = WindowMode.standard; + characterSettings = null; + } + else + { + characterSettings = workingList.Where(t => t.guid == info.guid).FirstOrDefault(); + if (windowMode == WindowMode.standard) + windowMode = WindowMode.extended; + } + } + else + { + characterSettings = workingList.Where(t => t.guid == info.guid).FirstOrDefault(); + if (windowMode == WindowMode.standard) + windowMode = WindowMode.extended; + } + SetWindowSize(); + Repaint(); + } + } + GUI.EndScrollView(); + } + + private void OnGUIExtendedSettingsArea(Rect optionBlock) + { + GUILayout.BeginArea(optionBlock); + if (characterSettings != null) + { + GUILayout.BeginVertical(); + GUILayout.Space(SETTINGS_TOP_PADDING); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Label(characterSettings.name, windowStyles.boldStyle); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.Space(10f); + + GUILayout.BeginHorizontal(); + + GUILayout.FlexibleSpace(); + + GUILayout.BeginVertical(); + + if (characterSettings.Generation == BaseGeneration.Unknown) + { + if (EditorGUILayout.DropdownButton( + content: new GUIContent("Rig Type: " + characterSettings.UnknownRigType.ToString()), + focusType: FocusType.Passive)) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Rig Type: None"), characterSettings.UnknownRigType == CharacterInfo.RigOverride.None, RigOptionSelected, CharacterInfo.RigOverride.None); + menu.AddItem(new GUIContent("Rig Type: Humanoid"), characterSettings.UnknownRigType == CharacterInfo.RigOverride.Humanoid, RigOptionSelected, CharacterInfo.RigOverride.Humanoid); + menu.AddItem(new GUIContent("Rig Type: Generic"), characterSettings.UnknownRigType == CharacterInfo.RigOverride.Generic, RigOptionSelected, CharacterInfo.RigOverride.Generic); + menu.ShowAsContext(); + } + + GUILayout.Space(1f); + } + + if (EditorGUILayout.DropdownButton( + content: new GUIContent(characterSettings.BasicMaterials ? "Basic Materials" : "High Quality Materials"), + focusType: FocusType.Passive)) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Basic Materials"), characterSettings.BasicMaterials, MaterialOptionSelected, true); + if (characterSettings.CanHaveHighQualityMaterials) + menu.AddItem(new GUIContent("High Quality Materials"), characterSettings.HQMaterials, MaterialOptionSelected, false); + menu.ShowAsContext(); + } + + GUILayout.Space(1f); + + if (characterSettings.BasicMaterials) GUI.enabled = false; + if (EditorGUILayout.DropdownButton( + content: new GUIContent(characterSettings.QualEyes.ToString() + " Eyes"), + focusType: FocusType.Passive)) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Basic Eyes"), characterSettings.BasicEyes, EyeOptionSelected, CharacterInfo.EyeQuality.Basic); + menu.AddItem(new GUIContent("Parallax Eyes"), characterSettings.ParallaxEyes, EyeOptionSelected, CharacterInfo.EyeQuality.Parallax); + if (Pipeline.isHDRP) + menu.AddItem(new GUIContent("Refractive (SSR) Eyes"), characterSettings.RefractiveEyes, EyeOptionSelected, CharacterInfo.EyeQuality.Refractive); + menu.ShowAsContext(); + } + + GUILayout.Space(1f); + string hairType; + switch (characterSettings.QualHair) + { + case CharacterInfo.HairQuality.TwoPass: hairType = "Two Pass Hair"; break; + case CharacterInfo.HairQuality.Coverage: hairType = "MSAA Coverage Hair"; break; + default: + case CharacterInfo.HairQuality.Default: hairType = "Single Pass Hair"; break; + } + if (EditorGUILayout.DropdownButton( + content: new GUIContent(hairType), + focusType: FocusType.Passive)) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Single Pass Hair"), characterSettings.DefaultHair, HairOptionSelected, CharacterInfo.HairQuality.Default); + menu.AddItem(new GUIContent("Two Pass Hair"), characterSettings.DualMaterialHair, HairOptionSelected, CharacterInfo.HairQuality.TwoPass); + if (Importer.USE_AMPLIFY_SHADER && !Pipeline.isHDRP) + 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()) + { + ValidateSettings(characterSettings); + } + GUI.enabled = true; + + GUILayout.Space(8f); + + if (characterSettings.BuiltBasicMaterials) GUI.enabled = false; + if (EditorGUILayout.DropdownButton( + content: new GUIContent(characterSettings.BakeCustomShaders ? "Bake Custom Shaders" : "Bake Default Shaders"), + focusType: FocusType.Passive)) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Default Shaders"), !characterSettings.BakeCustomShaders, BakeShadersOptionSelected, false); + menu.AddItem(new GUIContent("Custom Shaders"), characterSettings.BakeCustomShaders, BakeShadersOptionSelected, true); + menu.ShowAsContext(); + } + + GUILayout.Space(1f); + + if (EditorGUILayout.DropdownButton( + new GUIContent(characterSettings.BakeSeparatePrefab ? "Bake Separate Prefab" : "Bake Overwrite Prefab"), + FocusType.Passive + )) + { + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Overwrite Prefab"), !characterSettings.BakeSeparatePrefab, BakePrefabOptionSelected, false); + menu.AddItem(new GUIContent("Separate Baked Prefab"), characterSettings.BakeSeparatePrefab, BakePrefabOptionSelected, true); + menu.ShowAsContext(); + } + GUI.enabled = true; + + GUILayout.Space(12f); + + GUILayout.BeginHorizontal(); + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Apply to Selected", GUILayout.Width(140f), GUILayout.Height(32f))) + { + CopyCurrentSettingsToSelected(); + } + + GUILayout.FlexibleSpace(); + + GUILayout.EndHorizontal(); + + GUILayout.FlexibleSpace(); + + GUILayout.EndHorizontal(); + + GUILayout.FlexibleSpace(); + + GUILayout.EndVertical(); + + GUILayout.EndVertical(); + } + GUILayout.EndArea(); + } + + private void CopyCurrentSettingsToSelected() + { + bool dirty = false; + + if (characterSettings != null) + { + characterSettings.FixCharSettings(); + + foreach (CharacterInfo character in workingList) + { + if (character != characterSettings && character.selectedInList) + { + character.CopySettings(characterSettings); + if (ValidateSettings(character)) + dirty = true; + } + } + } + + if (dirty) + { + FilterDisplayedList(); + } + } + + private void ToggleSelectAllDisplayed() + { + foreach (CharacterListDisplay character in displayList) + { + character.selectedInList = isMassSelected; + CharacterInfo workingListChar = workingList.Where(t => t.guid == character.guid).FirstOrDefault(); + if (workingListChar != null) + { + workingListChar.selectedInList = isMassSelected; + } + } + } + + + private bool ValidateSettings(CharacterInfo characterSettings, bool refresh = true) + { + bool dirty = false; + if (characterSettings != null) + { + // find the original settings in validCharacters + CharacterInfo original = ImporterWindow.ValidCharacters.Where(x => x.guid == characterSettings.guid).FirstOrDefault(); + if (original != null) + { + if (characterSettings.UnknownRigType != original.UnknownRigType) dirty = true; + if (characterSettings.BasicMaterials != original.BasicMaterials) dirty = true; + if (characterSettings.HQMaterials != original.HQMaterials) dirty = true; + if (characterSettings.BasicEyes != original.BasicEyes) dirty = true; + if (characterSettings.ParallaxEyes != original.ParallaxEyes) dirty = true; + if (characterSettings.RefractiveEyes != original.RefractiveEyes) dirty = true; + if (characterSettings.DefaultHair != original.DefaultHair) dirty = true; + if (characterSettings.DualMaterialHair != original.DualMaterialHair) dirty = true; + if (characterSettings.CoverageHair != original.CoverageHair) dirty = true; + if (characterSettings.ShaderFlags != original.ShaderFlags) dirty = true; + if (characterSettings.BakeCustomShaders != original.BakeCustomShaders) dirty = true; + if (characterSettings.BakeSeparatePrefab != original.BakeSeparatePrefab) dirty = true; + } + } + characterSettings.settingsChanged = dirty; + if (refresh) FilterDisplayedList(); + return dirty; + } + + private void OnGUIGeneralDragBarArea(Rect dragBarRect) + { + Rect dragHandle = new Rect(dragBarRect.x, dragBarRect.y, DRAG_BAR_WIDTH + DRAG_HANDLE_PADDING, dragBarRect.height); + EditorGUIUtility.AddCursorRect(dragHandle, MouseCursor.ResizeHorizontal); + HandleMouseDrag(dragHandle); + + GUILayout.BeginArea(dragBarRect); + GUILayout.BeginVertical(windowStyles.dragBarStyle); + GUILayout.EndVertical(); + GUILayout.EndArea(); + } + + private void HandleMouseDrag(Rect container) + { + Event mouseEvent = Event.current; + if (container.Contains(mouseEvent.mousePosition) || dragging) + { + if (mouseEvent.type == EventType.MouseDrag) + { + dragging = true; + + // original block + PROC_LIST_WIDTH += mouseEvent.delta.x; + if (PROC_LIST_WIDTH < PROC_LIST_MIN_W) + PROC_LIST_WIDTH = PROC_LIST_MIN_W; + + RepaintOnUpdate(); + } + + if (mouseEvent.type == EventType.MouseUp) + { + dragging = false; + RepaintOnceOnUpdate(); + } + } + } + + private bool HandleListClick(Rect container) + { + Event mouseEvent = Event.current; + if (container.Contains(mouseEvent.mousePosition)) + { + if (mouseEvent.type == EventType.MouseDown) + { + if (mouseEvent.clickCount == 2) + { + //fakeButtonDoubleClick = true; + } + else + { + //fakeButtonDoubleClick = false; + } + return true; + } + } + return false; + } + + public class Styles + { + public GUIStyle iconStyle; + public GUIStyle dragBarStyle; + public GUIStyle fakeButton; + public GUIStyle fakeButtonContext; + public GUIStyle nameTextStyle; + public GUIStyle boldStyle; + public Texture2D dragTex, contextTex; + + public Styles() + { + iconStyle = new GUIStyle(); + iconStyle.wordWrap = false; + iconStyle.fontStyle = FontStyle.Normal; + iconStyle.normal.textColor = Color.white; + iconStyle.alignment = TextAnchor.MiddleCenter; + + dragBarStyle = new GUIStyle(); + dragBarStyle.normal.background = dragTex; + dragBarStyle.stretchHeight = true; + dragBarStyle.stretchWidth = true; + + fakeButton = new GUIStyle(); + fakeButton.padding = new RectOffset(1, 1, 1, 1); + fakeButton.stretchHeight = true; + fakeButton.stretchWidth = true; + + fakeButtonContext = new GUIStyle(); + fakeButtonContext.name = "fakeButtonContext"; + fakeButtonContext.normal.background = contextTex; + fakeButtonContext.padding = new RectOffset(1, 1, 1, 1); + fakeButtonContext.stretchHeight = true; + fakeButtonContext.stretchWidth = true; + + nameTextStyle = new GUIStyle(); + nameTextStyle.alignment = TextAnchor.MiddleLeft; + nameTextStyle.wordWrap = false; + nameTextStyle.fontStyle = FontStyle.Normal; + nameTextStyle.normal.textColor = Color.white; + + boldStyle = new GUIStyle(); + boldStyle.alignment = TextAnchor.UpperLeft; + boldStyle.wordWrap = false; + boldStyle.fontStyle = FontStyle.Bold; + boldStyle.normal.textColor = Color.white; + + FixTexturesOnDomainReload(); + } + + public void FixTexturesOnDomainReload() + { + if (!dragTex) + { + dragTex = TextureColor(new Color(0f, 0f, 0f, 0.25f)); + dragBarStyle.normal.background = dragTex; + } + if (!contextTex) + { + contextTex = TextureColor(new Color(0.259f, 0.345f, 0.259f)); + fakeButtonContext.normal.background = contextTex; + } + } + } + + void RepaintOnUpdate() + { + if (!repaintDelegated) + { + repaintDelegated = true; + EditorApplication.update -= RepaintOnceOnUpdate; + EditorApplication.update += RepaintOnceOnUpdate; + } + } + + void RepaintOnceOnUpdate() + { + Repaint(); + EditorApplication.update -= RepaintOnceOnUpdate; + repaintDelegated = false; + } + + 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 static Rect GetRectToCenterWindow(float width, float height) + { + //Rect appRect = GetEditorApplicationWindowRect(); // alas + Rect appRect = EditorGUIUtility.GetMainWindowPosition(); + + if (appRect == new Rect()) + { + return new Rect(100f, 100f, width, height); + } + + float xOrigin = appRect.x + (appRect.width / 2f) - (width / 2f); + float yOrigin = appRect.y + (appRect.height / 2f) - (height / 2f); + + return new Rect(xOrigin, yOrigin, width, height); + } + + public static Rect GetEditorApplicationWindowRect() + { + // The editor application's position is stored in: + // The current domain's assembly called UnityEditor.CoreModule (System.Reflection.Assembly) + // Inside the CoreModule the defined type ContainerWindow (System.Reflection.TypeInfo) + + // All Unity application windows are objects of type ContainerWindow (as above) + // Each window has a "position" 'property' and a "m_ShowMode" 'field' + // Get a field object for "m_ShowMode" and a property object for "position" + // Iterate through the windows obtained with Resources.FindObjectsOfTypeAll + // The main window has the field m_ShowMode == 4 (field object .GetValue(window)) + // The main window is obtained with property object .GetValue(window) + + System.Reflection.Assembly coreModuleAssembly = AppDomain.CurrentDomain.GetAssemblies().Where(t => t.FullName.Contains("UnityEditor.CoreModule")).FirstOrDefault(); + if (coreModuleAssembly != null) + { + System.Reflection.TypeInfo containerWindowTypeInfo = coreModuleAssembly.DefinedTypes.Where(t => t.FullName.Contains("ContainerWindow")).FirstOrDefault(); + if (containerWindowTypeInfo != null) + { + var showModeField = containerWindowTypeInfo.GetField("m_ShowMode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var positionProperty = containerWindowTypeInfo.GetProperty("position", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + if (showModeField != null && positionProperty != null) + { + var allContainerWindows = Resources.FindObjectsOfTypeAll(containerWindowTypeInfo); + foreach (var win in allContainerWindows) + { + var showmode = (int)showModeField.GetValue(win); + if (showmode == 4) // main window + { + var mainWindowPosition = (Rect)positionProperty.GetValue(win, null); + return mainWindowPosition; + } + } + } + } + } + return new Rect(0f, 0f, 0f, 0f); // something was null - return a new empty Rect + } + + private void FilterOptionSelected(object sel) + { + windowFilterType = (FilterType)sel; + FilterDisplayedList(); + } + + private void EyeOptionSelected(object sel) + { + characterSettings.QualEyes = (CharacterInfo.EyeQuality)sel; + ValidateSettings(characterSettings); + } + + private void RigOptionSelected(object sel) + { + characterSettings.UnknownRigType = (CharacterInfo.RigOverride)sel; + ValidateSettings(characterSettings); + } + + private void HairOptionSelected(object sel) + { + characterSettings.QualHair = (CharacterInfo.HairQuality)sel; + ValidateSettings(characterSettings); + } + + private void MaterialOptionSelected(object sel) + { + if ((bool)sel) + characterSettings.BuildQuality = MaterialQuality.Default; + else + characterSettings.BuildQuality = MaterialQuality.High; + ValidateSettings(characterSettings); + } + + private void BakeShadersOptionSelected(object sel) + { + characterSettings.BakeCustomShaders = (bool)sel; + ValidateSettings(characterSettings); + } + + private void BakePrefabOptionSelected(object sel) + { + characterSettings.BakeSeparatePrefab = (bool)sel; + ValidateSettings(characterSettings); + } + + private void OnDisable() + { + ResetWindow(); + buildQueue = null; + } + + private void OnEnable() + { + initDone = false; //if the window is open during play mode - then this forces a refresh of the character list after exiting play mode (currently the window is closed on entering play mode) + } + + private void ResetWindow() + { + characterSettings = null; + windowMode = WindowMode.standard; + SetWindowSize(); + } + + private void CloseWindow() + { + ResetWindow(); + buildQueue = null; + this.Close(); + } + } +} \ No newline at end of file diff --git a/Editor/MassProcessingWindow.cs.meta b/Editor/MassProcessingWindow.cs.meta new file mode 100644 index 0000000..0615a76 --- /dev/null +++ b/Editor/MassProcessingWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8b97b64f0d52bc47927ab91e96cb2ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/MeshUtil.cs b/Editor/MeshUtil.cs index 332b016..ff97bba 100644 --- a/Editor/MeshUtil.cs +++ b/Editor/MeshUtil.cs @@ -21,7 +21,6 @@ using UnityEngine.Rendering; using System.IO; using System.Collections.Generic; -using UnityEngine.Diagnostics; namespace Reallusion.Import { @@ -1121,7 +1120,9 @@ public TwoPassPair(Material s, Material a, Material b) } - public static GameObject Extract2PassHairMeshes(CharacterInfo info, GameObject prefabAsset, GameObject prefabInstance) + public static GameObject Extract2PassHairMeshes(CharacterInfo info, + GameObject prefabAsset, GameObject prefabInstance, + bool savePrefabAsset = false) { if (!prefabInstance) return null; @@ -1328,10 +1329,13 @@ public static GameObject Extract2PassHairMeshes(CharacterInfo info, GameObject p if (prefabInstance && processCount > 0) { - Util.LogInfo("Updating character prefab..."); - // save the clone as the prefab for this character - string prefabPath = AssetDatabase.GetAssetPath(prefabAsset); - prefabAsset = PrefabUtility.SaveAsPrefabAsset(prefabInstance, prefabPath); + if (savePrefabAsset) + { + Util.LogInfo("Updating character prefab..."); + // save the clone as the prefab for this character + string prefabPath = AssetDatabase.GetAssetPath(prefabAsset); + prefabAsset = PrefabUtility.SaveAsPrefabAsset(prefabInstance, prefabPath); + } } else { diff --git a/Editor/Physics.cs b/Editor/Physics.cs index f29b91e..0b9643b 100644 --- a/Editor/Physics.cs +++ b/Editor/Physics.cs @@ -16,7 +16,6 @@ * along with CC_Unity_Tools. If not, see . */ -using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; @@ -277,7 +276,7 @@ private void ReadPhysicsJson(QuickJSON physicsJson, QuickJSON characterJson) } } - public GameObject AddPhysics() + public GameObject AddPhysics(bool savePrefabAsset = false) { bool animationMode = WindowManager.StopAnimationMode(); @@ -285,7 +284,10 @@ public GameObject AddPhysics() AddCloth(); AddSpringBones(); - prefabAsset = PrefabUtility.SaveAsPrefabAsset(prefabInstance, AssetDatabase.GetAssetPath(prefabAsset)); + if (savePrefabAsset) + { + prefabAsset = PrefabUtility.SaveAsPrefabAsset(prefabInstance, AssetDatabase.GetAssetPath(prefabAsset)); + } WindowManager.RestartAnimationMode(animationMode); @@ -359,6 +361,17 @@ private void AddColliders() colliderLookup.Add(c, collider.boneName); if (existingCollider) existingLookup.Add(c, existingCollider); } + 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; + colliderLookup.Add(c, collider.boneName); + if (existingCollider) existingLookup.Add(c, existingCollider); + } else { //BoxCollider b = g.gameObject.AddComponent(); @@ -459,7 +472,7 @@ private void AddColliders() { foreach (CollisionShapeData collider in boneColliders) { - if (collider.colliderType == ColliderType.Capsule && springColliderBones.Contains(collider.boneName)) + if (springColliderBones.Contains(collider.boneName)) { string colliderName = collider.boneName + "_" + collider.name; Transform bone = FindBone(collider.boneName); @@ -761,7 +774,7 @@ private void DoCloth(GameObject clothTarget, string meshName) settings.name = sourceName; settings.activate = data.activate; settings.gravity = data.gravity; - settings.selfCollision = data.selfCollision; + settings.selfCollision = Importer.USE_SELF_COLLISION ? data.selfCollision : false; settings.softRigidCollision = data.softRigidCollision; settings.softRigidMargin = data.softRigidMargin; @@ -874,7 +887,7 @@ public static GameObject RebuildPhysics(CharacterInfo characterInfo) { characterInfo.ShaderFlags |= CharacterInfo.ShaderFeatureFlags.ClothPhysics; Physics physics = new Physics(characterInfo, prefabAsset, prefabInstance); - physics.AddPhysics(); + physics.AddPhysics(true); characterInfo.Write(); } diff --git a/Editor/Pipeline.cs b/Editor/Pipeline.cs index 637d199..81deb84 100644 --- a/Editor/Pipeline.cs +++ b/Editor/Pipeline.cs @@ -19,10 +19,7 @@ using System.Collections.Generic; using UnityEditor; using UnityEngine; -using UnityEngine.Diagnostics; using UnityEngine.Rendering; -using System.IO; -using System; #if HDRP_10_5_0_OR_NEWER using UnityEngine.Rendering.HighDefinition; using UnityEditor.Rendering.HighDefinition; @@ -43,7 +40,7 @@ public enum MaterialQuality { None, Default, High, Baked } public static class Pipeline { - public const string VERSION = "1.4.9"; + public const string VERSION = "1.5.0"; #if HDRP_10_5_0_OR_NEWER // version @@ -545,8 +542,7 @@ public static void AddDiffusionProfilesHDRP() SerializedObject hdrp = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath(assetPath)[0]); if (hdrp == null) return; - - int index; + bool modified = false; string[] profiles = new string[] { "RL_Skin_Profile", "RL_Teeth_Profile", "RL_Eye_Profile", "RL_SSS_Profile" }; @@ -593,6 +589,8 @@ public static void AddDiffusionProfilesHDRP() if (modified) { dpl.diffusionProfiles.value = dpsList.ToArray(); + EditorUtility.SetDirty(defaultVolumeAsset); + AssetDatabase.SaveAssetIfDirty(defaultVolumeAsset); } #else SerializedProperty list = hdrp.FindProperty("diffusionProfileSettingsList"); @@ -615,7 +613,7 @@ public static void AddDiffusionProfilesHDRP() if (add) { - index = list.arraySize; + int index = list.arraySize; if (index < 15) { list.InsertArrayElementAtIndex(index); @@ -794,15 +792,15 @@ public static string GetTemplateMaterialName(string sourceName, MaterialType mat } public static Material GetTemplateMaterial(string sourceName, MaterialType materialType, MaterialQuality quality, - CharacterInfo info, bool useAmplify = false, bool useTessellation = false, bool useWrinkleMaps = false) + CharacterInfo info, bool useAmplify = false, bool useTessellation = false, bool useWrinkleMaps = false, bool useDigitalHuman = false) { string templateName = GetTemplateMaterialName(sourceName, materialType, quality, info); - return GetUpgradedTemplateMaterial(sourceName, templateName, quality, useAmplify, useTessellation, useWrinkleMaps); + return GetUpgradedTemplateMaterial(sourceName, templateName, quality, useAmplify, useTessellation, useWrinkleMaps, useDigitalHuman); } public static Material GetUpgradedTemplateMaterial(string sourceName, string templateName, MaterialQuality quality, - bool useAmplify, bool useTessellation, bool useWrinkleMaps) + bool useAmplify, bool useTessellation, bool useWrinkleMaps, bool useDigitalHuman) { string customTemplateName; Material customTemplate = null; @@ -819,6 +817,17 @@ public static Material GetUpgradedTemplateMaterial(string sourceName, string tem } } + if (useDigitalHuman) + { + customTemplateName = templateName + "_DH"; + foundTemplate = Util.FindMaterial(customTemplateName); + if (foundTemplate) + { + templateName = customTemplateName; + customTemplate = foundTemplate; + } + } + if (useTessellation) { customTemplateName = templateName + "_T"; diff --git a/Editor/PreviewScene.cs b/Editor/PreviewScene.cs index b901ef0..4b82726 100644 --- a/Editor/PreviewScene.cs +++ b/Editor/PreviewScene.cs @@ -16,11 +16,9 @@ * along with CC_Unity_Tools. If not, see . */ -using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; -using UnityEditor.SceneManagement; using UnityEngine.SceneManagement; #if UNITY_POST_PROCESSING_3_1_1 using UnityEngine.Rendering.PostProcessing; @@ -137,6 +135,23 @@ public static void RestoreLighting() } } + public static void PokeLighting() + { + PreviewScene ps = WindowManager.GetPreviewScene(); + + List lightingContainers = new List(); + Util.FindSceneObjects(ps.lighting, "LightingConfig", lightingContainers); + + for (int i = 0; i < lightingContainers.Count; i++) + { + if (lightingContainers[i].activeInHierarchy) + { + lightingContainers[i].SetActive(false); + lightingContainers[i].SetActive(true); + } + } + } + public GameObject GetPreviewCharacter() { if (character.transform.childCount > 0) @@ -250,6 +265,7 @@ public GameObject UpdatePreviewCharacter(GameObject prefabAsset) public void PostProcessingAndLighting() { + Util.LogInfo("PostProcessingAndLighting"); if (Pipeline.is3D || Pipeline.isURP) { Material skybox = (Material)Util.FindAsset("RL Preview Gradient Skybox"); @@ -269,9 +285,10 @@ public void PostProcessingAndLighting() ppl.volumeTrigger = camera.transform; LayerMask everything = ~0; ppl.volumeLayer = everything; - ppl.antialiasingMode = PostProcessLayer.Antialiasing.TemporalAntialiasing; + ppl.antialiasingMode = PostProcessLayer.Antialiasing.TemporalAntialiasing; ppv.isGlobal = true; - ppv.profile = volume; + //ppv.profile = volume; + ppv.sharedProfile = volume; } #endif diff --git a/Editor/QuickAnimProcess.cs b/Editor/QuickAnimProcess.cs index b6b3fc3..d55e692 100644 --- a/Editor/QuickAnimProcess.cs +++ b/Editor/QuickAnimProcess.cs @@ -32,6 +32,7 @@ */ +using Reallusion.Import; using System.IO; using System.Text.RegularExpressions; using UnityEditor; @@ -233,7 +234,7 @@ private static void WriteAnimationClip(Object o, AnimationClip animationClip) if (AssetPathIsEmpty(fullOutputPath)) break; } } - Debug.Log("Writing Asset: " + fullOutputPath); + Util.LogInfo("Writing Asset: " + fullOutputPath); AssetDatabase.CreateAsset(animationClip, fullOutputPath); } diff --git a/Editor/RL.cs b/Editor/RL.cs index b1ebe46..a1d6efa 100644 --- a/Editor/RL.cs +++ b/Editor/RL.cs @@ -119,7 +119,7 @@ public static BaseGeneration GetCharacterGeneration(GameObject fbx, string gener return BaseGeneration.Unknown; } - public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer, CharacterInfo info) + public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer, CharacterInfo info, Avatar avatar = null) { // import normals to avoid mesh smoothing issues // importing blend shape normals gives disasterously bad results, they need to be recalculated, @@ -172,21 +172,31 @@ public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer return; } - HumanDescription human = importer.humanDescription; - Func Bone = (humanName, boneName) => new HumanBone() + if (avatar) { - humanName = humanName, - boneName = boneName - }; - List boneList = new List(); - - #region HumanBoneDescription - if (info.Generation == BaseGeneration.G3 || - info.Generation == BaseGeneration.G3Plus || - info.Generation == BaseGeneration.ActorCore || - info.Generation == BaseGeneration.ActorBuild) + importer.avatarSetup = ModelImporterAvatarSetup.CopyFromOther; + + importer.sourceAvatar = avatar; + } + else { - boneList = new List { + importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel; + + HumanDescription human = importer.humanDescription; + Func Bone = (humanName, boneName) => new HumanBone() + { + humanName = humanName, + boneName = boneName + }; + List boneList = new List(); + + #region HumanBoneDescription + if (info.Generation == BaseGeneration.G3 || + info.Generation == BaseGeneration.G3Plus || + info.Generation == BaseGeneration.ActorCore || + info.Generation == BaseGeneration.ActorBuild) + { + boneList = new List { Bone("Chest", "CC_Base_Spine01"), Bone("Head", "CC_Base_Head"), Bone("Hips", "CC_Base_Hip"), @@ -243,10 +253,10 @@ public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer Bone("Spine", "CC_Base_Waist"), Bone("UpperChest", "CC_Base_Spine02"), }; - } - else if (info.Generation == BaseGeneration.G1) - { - boneList = new List { + } + else if (info.Generation == BaseGeneration.G1) + { + boneList = new List { Bone("Chest", "CC_Base_Spine01"), Bone("Head", "CC_Base_Head"), Bone("Hips", "CC_Base_Hip"), @@ -303,10 +313,10 @@ public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer Bone("Spine", "CC_Base_Waist"), Bone("UpperChest", "CC_Base_Spine02"), }; - } - else if (info.Generation == BaseGeneration.GameBase) - { - boneList = new List { + } + else if (info.Generation == BaseGeneration.GameBase) + { + boneList = new List { Bone("Chest", "spine_02"), Bone("Head", "head"), Bone("Hips", "pelvis"), @@ -363,52 +373,53 @@ public static void HumanoidImportSettings(GameObject fbx, ModelImporter importer Bone("Spine", "spine_01"), Bone("UpperChest", "spine_03"), }; - } + } - // clean up bone list for missing bones (from bone LOD exports) - for (int b = 0; b < boneList.Count; b++) - { - if (Util.FindChildRecursive(fbx.transform, boneList[b].boneName) == null) + // clean up bone list for missing bones (from bone LOD exports) + for (int b = 0; b < boneList.Count; b++) { - //Debug.LogWarning("Missing bone: " + boneList[b].boneName); - boneList.RemoveAt(b--); + if (Util.FindChildRecursive(fbx.transform, boneList[b].boneName) == null) + { + //Debug.LogWarning("Missing bone: " + boneList[b].boneName); + boneList.RemoveAt(b--); + } } - } - - if (boneList.Count > 0) - human.human = boneList.ToArray(); - #endregion + if (boneList.Count > 0) + human.human = boneList.ToArray(); - for (int i = 0; i < human.human.Length; ++i) - { - human.human[i].limit.useDefaultValues = true; - } + #endregion - human.upperArmTwist = 0.5f; - human.lowerArmTwist = 0.5f; - human.upperLegTwist = 0.5f; - human.lowerLegTwist = 0.5f; - human.armStretch = 0.05f; - human.legStretch = 0.05f; - human.feetSpacing = 0.0f; - human.hasTranslationDoF = true; + for (int i = 0; i < human.human.Length; ++i) + { + human.human[i].limit.useDefaultValues = true; + } - if (info.JsonData != null) - { - Transform[] transforms = fbx.GetComponentsInChildren(); - SkeletonBone[] bones = new SkeletonBone[transforms.Length]; - for (int i = 0; i < transforms.Length; i++) + human.upperArmTwist = 0.5f; + human.lowerArmTwist = 0.5f; + human.upperLegTwist = 0.5f; + human.lowerLegTwist = 0.5f; + human.armStretch = 0.05f; + human.legStretch = 0.05f; + human.feetSpacing = 0.0f; + human.hasTranslationDoF = true; + + if (info.JsonData != null) { - bones[i].name = transforms[i].name; - bones[i].position = transforms[i].localPosition; - bones[i].rotation = transforms[i].localRotation; - bones[i].scale = transforms[i].localScale; + Transform[] transforms = fbx.GetComponentsInChildren(); + SkeletonBone[] bones = new SkeletonBone[transforms.Length]; + for (int i = 0; i < transforms.Length; i++) + { + bones[i].name = transforms[i].name; + bones[i].position = transforms[i].localPosition; + bones[i].rotation = transforms[i].localRotation; + bones[i].scale = transforms[i].localScale; + } + human.skeleton = bones; } - human.skeleton = bones; - } - importer.humanDescription = human; + importer.humanDescription = human; + } } public static AnimatorController AutoCreateAnimator(GameObject fbx, string assetPath, ModelImporter importer) @@ -428,20 +439,45 @@ public static AnimatorController AutoCreateAnimator(GameObject fbx, string asset AnimatorStateMachine stateMachine = controller.layers[0].stateMachine; UnityEngine.Object[] assets = AssetDatabase.LoadAllAssetsAtPath(assetPath); + AnimationClip TPoseClip = null; + AnimationClip previewClip = null; + AnimationClip foundClip = null; foreach (UnityEngine.Object obj in assets) { - AnimationClip clip = obj as AnimationClip; - clip = AnimRetargetGUI.TryGetRetargetedAnimationClip(fbx, clip); - - if (clip) + if (obj.GetType() == typeof(AnimationClip)) { - if (clip.name.iContains("__preview__") || clip.name.iContains("t-pose")) - continue; + AnimationClip clip = obj as AnimationClip; + clip = AnimRetargetGUI.TryGetRetargetedAnimationClip(fbx, clip); + if (clip) + { + if (!clip.name.iContains("__preview__") && clip.name.iContains("t-pose")) + { + TPoseClip = clip; + continue; + } - controller.AddMotion(clip, 0); + if (clip.name.iContains("__preview__")) + { + previewClip = clip; + continue; + } + + controller.AddMotion(clip, 0); + foundClip = clip; + break; + } } } + if (!foundClip && TPoseClip) + { + controller.AddMotion(TPoseClip, 0); + } + else if (!foundClip && previewClip) + { + controller.AddMotion(previewClip, 0); + } + if (AssetDatabase.WriteImportSettingsIfDirty(assetPath)) { AssetDatabase.SaveAssets(); @@ -498,10 +534,10 @@ public static void SetupAnimation(ModelImporter importer, CharacterInfo characte if (changed) { - importer.clipAnimations = animations; + importer.clipAnimations = animations; if (forceUpdate) { - AssetDatabase.WriteImportSettingsIfDirty(characterInfo.path); + AssetDatabase.WriteImportSettingsIfDirty(importer.assetPath); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } @@ -510,13 +546,34 @@ public static void SetupAnimation(ModelImporter importer, CharacterInfo characte characterInfo.animationSetup = true; } - public static void SetAnimationImport(CharacterInfo info, GameObject fbx) - { - ModelImporter importer = (ModelImporter)AssetImporter.GetAtPath(info.path); + public static void DoAnimationImport(CharacterInfo info, GameObject fbx) + { + string path = info.path; + ModelImporter importer = (ModelImporter)AssetImporter.GetAtPath(path); + HumanoidImportSettings(fbx, importer, info); SetupAnimation(importer, info, true); ApplyAnimatorController(info, AutoCreateAnimator(fbx, info.path, importer)); + + Avatar sourceAvatar = info.GetCharacterAvatar(); + + List motionGuids = info.GetMotionGuids(); + if (motionGuids.Count > 0) + { + foreach (string guid in motionGuids) + { + string assetPath = AssetDatabase.GUIDToAssetPath(guid); + DoMotionImport(info, sourceAvatar, assetPath); + } + } } + public static void DoMotionImport(CharacterInfo info, Avatar sourceAvatar, string motionFbxPath) + { + ModelImporter importer = (ModelImporter)AssetImporter.GetAtPath(motionFbxPath); + HumanoidImportSettings(null, importer, info, sourceAvatar); + SetupAnimation(importer, info, true); + } + public static void ApplyAnimatorController(CharacterInfo info, AnimatorController controller) { string prefabFolder = Util.CreateFolder(info.folder, Importer.PREFABS_FOLDER); @@ -590,16 +647,23 @@ public static GameObject CreatePrefabFromModel(CharacterInfo info, GameObject fb sceneInstance.GetComponent().cullingMode = AnimatorCullingMode.CullUpdateTransforms; } - GameObject prefab = PrefabUtility.SaveAsPrefabAsset(sceneInstance, prefabPath); - + bool assetExists = Util.AssetPathExists(prefabPath); + if (assetExists) + { + AssetDatabase.DeleteAsset(prefabPath); + } + GameObject prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(sceneInstance, prefabPath, InteractionMode.AutomatedAction); return prefab; } public static GameObject SaveAndRemovePrefabInstance(GameObject prefabAsset, GameObject prefabInstance) - { - GameObject prefab = PrefabUtility.SaveAsPrefabAsset(prefabInstance, AssetDatabase.GetAssetPath(prefabAsset)); + { + //PrefabUtility.SavePrefabAsset(prefabInstance); + string assetPath = AssetDatabase.GetAssetPath(prefabAsset); + GameObject prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(prefabInstance, assetPath, + InteractionMode.AutomatedAction); UnityEngine.Object.DestroyImmediate(prefabInstance); - return prefab; + return prefabAsset; } public static int CountLODs(GameObject fbx) @@ -720,7 +784,8 @@ public static GameObject CreateOneLODPrefabFromModel(CharacterInfo info, GameObj lodGroup.RecalculateBounds(); } - GameObject prefab = PrefabUtility.SaveAsPrefabAsset(sceneLODInstance, prefabPath); + GameObject prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(sceneLODInstance, prefabPath, + InteractionMode.AutomatedAction); return prefab; } diff --git a/Editor/ShaderPackageManifest.cs b/Editor/ShaderPackageManifest.cs index 62d691d..775a0c2 100644 --- a/Editor/ShaderPackageManifest.cs +++ b/Editor/ShaderPackageManifest.cs @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2021 Victor Soupday + * This file is part of CC_Unity_Tools + * + * CC_Unity_Tools is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * CC_Unity_Tools is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CC_Unity_Tools. If not, see . + */ + using UnityEditor; using UnityEngine; using System.IO; diff --git a/Editor/Util.cs b/Editor/Util.cs index 28ffc16..b144ad6 100644 --- a/Editor/Util.cs +++ b/Editor/Util.cs @@ -725,7 +725,6 @@ public static Material[] GetLinkedMaterials(Material mat) string searchName = Path.GetFileNameWithoutExtension(searchPath).ToLowerInvariant(); if (searchName.Contains(name)) { - //Debug.Log(searchName); results.Add(AssetDatabase.LoadAssetAtPath(searchPath)); } } @@ -846,13 +845,13 @@ public static AnimationClip GetFirstAnimationClipFromCharacter(GameObject source return found; } - public static AnimationClip[] GetAllAnimationClipsFromCharacter(GameObject sourceFbx) + public static AnimationClip[] GetAllAnimationClipsFromCharacter(string sourceFbxPath) { List clips = new List(); - if (sourceFbx) + if (!string.IsNullOrEmpty(sourceFbxPath)) { - Object[] data = AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GetAssetPath(sourceFbx)); + Object[] data = AssetDatabase.LoadAllAssetRepresentationsAtPath(sourceFbxPath); foreach (Object subObject in data) { if (subObject.GetType().Equals(typeof(AnimationClip))) @@ -884,6 +883,23 @@ public static GameObject FindCharacterPrefabAsset(GameObject fbxAsset, bool bake return null; } + public static GameObject FindCharacterPrefabAsset(string fbxPath, bool baked = false) + { + if (!string.IsNullOrEmpty(fbxPath)) + { + if (fbxPath.iEndsWith(".prefab")) + return AssetDatabase.LoadAssetAtPath(fbxPath); + string folder = Path.GetDirectoryName(fbxPath); + string name = Path.GetFileNameWithoutExtension(fbxPath); + string searchName = name; + if (baked) searchName = name + Importer.BAKE_SUFFIX; + string prefabPath = Path.Combine(folder, Importer.PREFABS_FOLDER, searchName + ".prefab"); + if (File.Exists(prefabPath)) + return AssetDatabase.LoadAssetAtPath(prefabPath); + } + return null; + } + public static bool FindCharacterPrefabs(GameObject fbxAsset, out GameObject mainPrefab, out GameObject bakedPrefab) { string path = AssetDatabase.GetAssetPath(fbxAsset); @@ -1069,7 +1085,12 @@ public static Transform FindChildRecursive(Transform root, string search) } return null; - } + } + + public static bool AssetPathExists(string assetPath) + { + return File.Exists(assetPath); + } public static bool AssetPathIsEmpty(string assetPath) { @@ -1091,7 +1112,7 @@ public static bool HasMaterialKeywords(GameObject obj, params string[] keywords) } return false; - } + } public static bool NameContainsKeywords(string name, params string[] keyword) { @@ -1120,6 +1141,189 @@ public static bool NameContainsKeywords(string name, params string[] keyword) return false; } + + + const string prefsFailString = "xxxxxxxxxxxxxx"; + const char delimiterChar = ','; + + public static bool TrySerializeAssetToEditorPrefs(Object asset, string editorPrefsKey) + { + int assetInstanceID = asset.GetInstanceID(); + 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); + + EditorPrefs.SetString(editorPrefsKey, outString); + return true; + } + else + { + string path = AssetDatabase.GetAssetPath(assetInstanceID); + LogWarn("Cannot get GUID and ID for: " + asset.name + " at path: " + path); + EditorPrefs.SetString(editorPrefsKey, prefsFailString); + return false; + } + } + + public static bool TryDeSerializeAssetFromEditorPrefs(out Object asset, string editorPrefsKey) + { + bool storedAsset = false; + string assetString = ""; + if (EditorPrefs.HasKey(editorPrefsKey)) + { + assetString = EditorPrefs.GetString(editorPrefsKey); + if (assetString == prefsFailString) + { + LogInfo("Asset storage had failed - no asset to recover"); + } + else + { + storedAsset = true; + } + } + else + { + LogWarn("No asset reference found"); + } + + if (storedAsset) + { + string[] split = assetString.Split(new char[] { delimiterChar }); + + LogInfo("assetString: " + assetString); + LogInfo("split count: " + split.Length); + + if (split.Length == 3) + { + int assetInstanceID = int.Parse(split[0]); + 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()); + + Object[] potentials = AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GUIDToAssetPath(guid)); + + LogInfo(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); + if (potentialAsset.GetType() == typeof(T)) + { + LogInfo("Successfully found single asset: " + potentialAsset.GetType().Name + " Named: " + potentialAsset.name); + asset = potentialAsset; + return true; + } + } + } + else + { + foreach (Object potential in potentials) + { + if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(potential.GetInstanceID(), out string tryGuid, out long tryLocalid)) + { + if (guid == tryGuid && tryLocalid == localid) + { + if (potential.GetType() == typeof(T)) + { + LogInfo("Successfully found embedded asset: " + potential.GetType().Name + " Named: " + potential.name); + asset = potential; + return true; + } + } + } + } + } + } + } + asset = null; + return false; + } + + public static void SerializeBoolToEditorPrefs(bool value, string editorPrefsKey) + { + EditorPrefs.SetBool(editorPrefsKey, value); + } + + public static bool TryDeSerializeBoolFromEditorPrefs(out bool value, string editorPrefsKey) + { + if (!EditorPrefs.HasKey(editorPrefsKey)) + { + value = false; + return false; + } + else + { + value = EditorPrefs.GetBool(editorPrefsKey); + return true; + } + } + + public static void SerializeFloatToEditorPrefs(float value, string editorPrefsKey) + { + EditorPrefs.SetFloat(editorPrefsKey, value); + } + + public static bool TryDeSerializeFloatFromEditorPrefs(out float value, string editorPrefsKey) + { + if (!EditorPrefs.HasKey(editorPrefsKey)) + { + value = 0f; + return false; + } + else + { + value = EditorPrefs.GetFloat(editorPrefsKey); + return true; + } + } + + public static void SerializeStringToEditorPrefs(string value, string editorPrefsKey) + { + EditorPrefs.SetString(editorPrefsKey, value); + } + + public static bool TryDeserializeStringFromEditorPrefs(out string value, string editorPrefsKey) + { + if (!EditorPrefs.HasKey(editorPrefsKey)) + { + value = null; + return false; + } + else + { + value = EditorPrefs.GetString(editorPrefsKey); + return true; + } + } + + public static void SerializeIntToEditorPrefs(int value, string editorPrefsKey) + { + EditorPrefs.SetInt(editorPrefsKey, value); + } + + public static bool TryDeserializeIntFromEditorPrefs(out int value, string editorPrefsKey) + { + if (!EditorPrefs.HasKey(editorPrefsKey)) + { + value = 0; + return false; + } + else + { + value = EditorPrefs.GetInt(editorPrefsKey); + return true; + } + } + public static void LogInfo(string message) { if (LOG_LEVEL >= 2) diff --git a/Editor/WeightMapperEditor.cs b/Editor/WeightMapperEditor.cs index a6a577d..db4413a 100644 --- a/Editor/WeightMapperEditor.cs +++ b/Editor/WeightMapperEditor.cs @@ -17,13 +17,7 @@ */ using UnityEngine; -using System.Collections; -using System.Collections.Generic; using UnityEditor; -using System.IO; -using System.Linq; -using PhysicsSettings = Reallusion.Import.WeightMapper.PhysicsSettings; -using ColliderSettings = Reallusion.Import.ColliderManager.ColliderSettings; namespace Reallusion.Import { diff --git a/Editor/WindowManager.cs b/Editor/WindowManager.cs index af6f3a5..1e1adc2 100644 --- a/Editor/WindowManager.cs +++ b/Editor/WindowManager.cs @@ -16,14 +16,11 @@ * along with CC_Unity_Tools. If not, see . */ -using System.Collections; -using System.Collections.Generic; using UnityEngine; -using UnityEngine.SceneManagement; using UnityEditor; using UnityEditor.SceneManagement; -using UnityEditor.Compilation; using System; +using Scene = UnityEngine.SceneManagement.Scene; namespace Reallusion.Import { @@ -36,6 +33,7 @@ public static class WindowManager public static bool openedInPreviewScene; public static bool showPlayer = true; public static bool showRetarget = false; + public static bool batchProcess = false; private static bool eventsAdded = false; private static bool showPlayerAfterPlayMode = false; private static bool showRetargetAfterPlayMode = false; @@ -44,6 +42,16 @@ public static class WindowManager public static OnTimer onTimer; private static float timer = 0f; + //unique editorprefs key names + public const string sceneFocus = "RL_Scene_Focus_Key_0000"; + public const string clipKey = "RL_Animation_Asset_Key_0000"; + public const string animatorControllerKey = "RL_Character_Animator_Ctrl_Key_0000"; + public const string trackingStatusKey = "RL_Bone_Tracking_Key_0000"; + public const string lastTrackedBoneKey = "RL_Last_Tracked_Bone_Key_0000"; + public const string controlStateHashKey = "RL_Animator_Ctrl_Hash_Key_0000"; + public const string timeKey = "RL_Animation_Play_Position_Key_0000"; + + static WindowManager() { EditorApplication.playModeStateChanged += WindowManager.OnPlayModeStateChanged; @@ -72,7 +80,82 @@ static WindowManager() } public static void OnPlayModeStateChanged(PlayModeStateChange state) - { + { + switch (state) + { + case PlayModeStateChange.ExitingEditMode: + { + Debug.Log(state); + + + break; + } + case PlayModeStateChange.EnteredPlayMode: + { + Debug.Log(state); + + showPlayerAfterPlayMode = showPlayer; + showRetargetAfterPlayMode = showRetarget; + showPlayer = false; + showRetarget = false; + AnimPlayerGUI.ClosePlayer(); + AnimRetargetGUI.CloseRetargeter(); + + + if (Util.TryDeSerializeBoolFromEditorPrefs(out bool val, WindowManager.sceneFocus)) + { + if (val) + { + Debug.Log("Reverting Scene Focus"); + //GrabLastSceneFocus(); + Util.SerializeBoolToEditorPrefs(false, WindowManager.sceneFocus); + ShowAnimationPlayer(); + if (Util.TryDeSerializeFloatFromEditorPrefs(out float timeCode, WindowManager.timeKey)) + { + //set the play position + AnimPlayerGUI.time = timeCode; + //slightly delay startup to allow the animator to initialize + AnimPlayerGUI.delayFrames = 2; + } + } + } + + if (Util.TryDeserializeIntFromEditorPrefs(out int hash, WindowManager.controlStateHashKey)) + { + AnimPlayerGUI.controlStateHash = hash; + } + + + if (Util.TryDeSerializeBoolFromEditorPrefs(out bool track, WindowManager.trackingStatusKey)) + { + AnimPlayerGUI.isTracking = track; + if (track) + { + if (Util.TryDeserializeStringFromEditorPrefs(out string bone, WindowManager.lastTrackedBoneKey)) + { + AnimPlayerGUI.ReEstablishTracking(bone); + } + } + Util.SerializeBoolToEditorPrefs(false, WindowManager.trackingStatusKey); + } + + break; + } + case PlayModeStateChange.ExitingPlayMode: + { + + break; + } + case PlayModeStateChange.EnteredEditMode: + { + Debug.Log(state); + showPlayer = showPlayerAfterPlayMode; + showRetarget = showRetargetAfterPlayMode; + + break; + } + } + /* if (state == PlayModeStateChange.EnteredPlayMode) { Debug.Log(state); @@ -82,6 +165,45 @@ public static void OnPlayModeStateChanged(PlayModeStateChange state) showRetarget = false; AnimPlayerGUI.ClosePlayer(); AnimRetargetGUI.CloseRetargeter(); + + /* + // original + Debug.Log(state); + showPlayerAfterPlayMode = showPlayer; + showRetargetAfterPlayMode = showRetarget; + showPlayer = false; + showRetarget = false; + AnimPlayerGUI.ClosePlayer(); + AnimRetargetGUI.CloseRetargeter(); + */ + /* + if (Util.TryDeSerializeBoolFromEditorPrefs(out bool val, WindowManager.sceneFocus)) + { + if (val) + { + Debug.Log("Reverting Scene Focus"); + SceneView.lastActiveSceneView.Focus(); + Util.SerializeBoolToEditorPrefs(false, WindowManager.sceneFocus); + ShowAnimationPlayer(); + if (Util.TryDeSerializeFloatFromEditorPrefs(out float timeCode, WindowManager.timeKey)) + { + //set the play position + AnimPlayerGUI.time = timeCode; + //slightly delay startup to allow the animator to initialize + AnimPlayerGUI.delayFrames = 10; + } + } + else //no scene view focus grab - replace the oringinal runtime animator controller + { + if (Util.TryDeSerializeBoolFromEditorPrefs(out bool restore, WindowManager.animatorControllerKey)) + { + if (restore) + { + AnimPlayerGUI.RestoreBaseAnimatorController(); + } + } + } + } } else if (state == PlayModeStateChange.EnteredEditMode) { @@ -89,6 +211,7 @@ public static void OnPlayModeStateChanged(PlayModeStateChange state) showPlayer = showPlayerAfterPlayMode; showRetarget = showRetargetAfterPlayMode; } + */ } public static void OnBeforeAssemblyReload() @@ -111,7 +234,7 @@ public static PreviewScene OpenPreviewScene(GameObject prefab) if (!prefab) return default; if (!IsPreviewScene && !EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return default; - Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); + UnityEngine.SceneManagement.Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); GameObject.Instantiate(Util.FindPreviewScenePrefab(), Vector3.zero, Quaternion.identity); previewSceneHandle = scene; @@ -334,10 +457,16 @@ public static GameObject GetSelectedOrPreviewCharacter() if (Selection.activeGameObject) { - GameObject selectedPrefab = Util.GetScenePrefabInstanceRoot(Selection.activeGameObject); - if (selectedPrefab && selectedPrefab.GetComponent()) + string s = AssetDatabase.GetAssetPath(Selection.activeObject); + if (string.IsNullOrEmpty(s)) { - characterPrefab = selectedPrefab; + Debug.Log("SELECTED PATH: IsNullOrEmpty"); + + GameObject selectedPrefab = Util.GetScenePrefabInstanceRoot(Selection.activeGameObject); + if (selectedPrefab && selectedPrefab.GetComponent()) + { + characterPrefab = selectedPrefab; + } } } @@ -349,6 +478,11 @@ public static GameObject GetSelectedOrPreviewCharacter() return characterPrefab; } + public static void GrabLastSceneFocus() + { + SceneView.lastActiveSceneView.Focus(); + } + public static void ShowAnimationPlayer() { GameObject scenePrefab = GetSelectedOrPreviewCharacter(); @@ -361,6 +495,10 @@ public static void ShowAnimationPlayer() if (showRetarget) ShowAnimationRetargeter(); showPlayer = true; + if (EditorApplication.isPlaying) + { + WindowManager.GrabLastSceneFocus(); + } } else { @@ -426,6 +564,6 @@ public static void RestartAnimationMode(bool inAnimationMode) public static void StartTimer(float delay) { timer = delay; - } + } } } diff --git a/Icons/RLIcon_BuildSettings.png b/Icons/RLIcon_BuildSettings.png new file mode 100644 index 0000000..27e9816 Binary files /dev/null and b/Icons/RLIcon_BuildSettings.png differ diff --git a/Icons/RLIcon_BuildSettings.png.meta b/Icons/RLIcon_BuildSettings.png.meta new file mode 100644 index 0000000..cd05d01 --- /dev/null +++ b/Icons/RLIcon_BuildSettings.png.meta @@ -0,0 +1,109 @@ +fileFormatVersion: 2 +guid: 81d3ee207866f1b40afc70f99b0ed170 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + vTOnly: 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: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 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/RLIcon_BuildSettingsShown.png b/Icons/RLIcon_BuildSettingsShown.png new file mode 100644 index 0000000..265efa9 Binary files /dev/null and b/Icons/RLIcon_BuildSettingsShown.png differ diff --git a/Icons/RLIcon_BuildSettingsShown.png.meta b/Icons/RLIcon_BuildSettingsShown.png.meta new file mode 100644 index 0000000..8dc76f2 --- /dev/null +++ b/Icons/RLIcon_BuildSettingsShown.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 50e7006fd65bd1645944de049676d55f +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_BuildSettingsShown_changed.png b/Icons/RLIcon_BuildSettingsShown_changed.png new file mode 100644 index 0000000..e025b54 Binary files /dev/null and b/Icons/RLIcon_BuildSettingsShown_changed.png differ diff --git a/Icons/RLIcon_BuildSettingsShown_changed.png.meta b/Icons/RLIcon_BuildSettingsShown_changed.png.meta new file mode 100644 index 0000000..5ce4961 --- /dev/null +++ b/Icons/RLIcon_BuildSettingsShown_changed.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 4e28e319c3868984c9facef2e691f906 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_BuildSettings_changed.png b/Icons/RLIcon_BuildSettings_changed.png new file mode 100644 index 0000000..dc23db3 Binary files /dev/null and b/Icons/RLIcon_BuildSettings_changed.png differ diff --git a/Icons/RLIcon_BuildSettings_changed.png.meta b/Icons/RLIcon_BuildSettings_changed.png.meta new file mode 100644 index 0000000..8841bf3 --- /dev/null +++ b/Icons/RLIcon_BuildSettings_changed.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 3974280c5d74657489990c4030ef4464 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_FilterEdit.png b/Icons/RLIcon_FilterEdit.png new file mode 100644 index 0000000..eee2ccd Binary files /dev/null and b/Icons/RLIcon_FilterEdit.png differ diff --git a/Icons/RLIcon_FilterEdit.png.meta b/Icons/RLIcon_FilterEdit.png.meta new file mode 100644 index 0000000..e250725 --- /dev/null +++ b/Icons/RLIcon_FilterEdit.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 1d96d6ee1a9cf9f4a8995b6e5083db5a +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_FilterList.png b/Icons/RLIcon_FilterList.png new file mode 100644 index 0000000..0fea751 Binary files /dev/null and b/Icons/RLIcon_FilterList.png differ diff --git a/Icons/RLIcon_FilterList.png.meta b/Icons/RLIcon_FilterList.png.meta new file mode 100644 index 0000000..65a2fa5 --- /dev/null +++ b/Icons/RLIcon_FilterList.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: ac5f74247539a7f4ebf2172fe3f282c0 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_FilterRemove.png b/Icons/RLIcon_FilterRemove.png new file mode 100644 index 0000000..cc5e96f Binary files /dev/null and b/Icons/RLIcon_FilterRemove.png differ diff --git a/Icons/RLIcon_FilterRemove.png.meta b/Icons/RLIcon_FilterRemove.png.meta new file mode 100644 index 0000000..3d901e4 --- /dev/null +++ b/Icons/RLIcon_FilterRemove.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 420a00e2cc66c76448b1f8059a366865 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_ListChecked.png b/Icons/RLIcon_ListChecked.png new file mode 100644 index 0000000..37043a6 Binary files /dev/null and b/Icons/RLIcon_ListChecked.png differ diff --git a/Icons/RLIcon_ListChecked.png.meta b/Icons/RLIcon_ListChecked.png.meta new file mode 100644 index 0000000..d3288b5 --- /dev/null +++ b/Icons/RLIcon_ListChecked.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 306689512f23671489994b7be021c42d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_ListRemove.png b/Icons/RLIcon_ListRemove.png new file mode 100644 index 0000000..fcd7652 Binary files /dev/null and b/Icons/RLIcon_ListRemove.png differ diff --git a/Icons/RLIcon_ListRemove.png.meta b/Icons/RLIcon_ListRemove.png.meta new file mode 100644 index 0000000..981a501 --- /dev/null +++ b/Icons/RLIcon_ListRemove.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 14b34cc4abb4f1f4599722d4bdc5edd0 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_ListUnchecked.png b/Icons/RLIcon_ListUnchecked.png new file mode 100644 index 0000000..f6b9efc Binary files /dev/null and b/Icons/RLIcon_ListUnchecked.png differ diff --git a/Icons/RLIcon_ListUnchecked.png.meta b/Icons/RLIcon_ListUnchecked.png.meta new file mode 100644 index 0000000..bb55081 --- /dev/null +++ b/Icons/RLIcon_ListUnchecked.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 7db2757acb706ab4f88718812598ccf8 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_RefreshList.png b/Icons/RLIcon_RefreshList.png new file mode 100644 index 0000000..e469d92 Binary files /dev/null and b/Icons/RLIcon_RefreshList.png differ diff --git a/Icons/RLIcon_RefreshList.png.meta b/Icons/RLIcon_RefreshList.png.meta new file mode 100644 index 0000000..483ddb3 --- /dev/null +++ b/Icons/RLIcon_RefreshList.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: a33e0e609a4d488419017a173b36406b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_SortList.png b/Icons/RLIcon_SortList.png new file mode 100644 index 0000000..b3cd9f0 Binary files /dev/null and b/Icons/RLIcon_SortList.png differ diff --git a/Icons/RLIcon_SortList.png.meta b/Icons/RLIcon_SortList.png.meta new file mode 100644 index 0000000..9e02199 --- /dev/null +++ b/Icons/RLIcon_SortList.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 50b0009361856c94989377b9d9182443 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Icons/RLIcon_StartProcessing.png b/Icons/RLIcon_StartProcessing.png new file mode 100644 index 0000000..6beccb7 Binary files /dev/null and b/Icons/RLIcon_StartProcessing.png differ diff --git a/Icons/RLIcon_StartProcessing.png.meta b/Icons/RLIcon_StartProcessing.png.meta new file mode 100644 index 0000000..926112a --- /dev/null +++ b/Icons/RLIcon_StartProcessing.png.meta @@ -0,0 +1,124 @@ +fileFormatVersion: 2 +guid: 624844febe1fcbd458f0969ce5e25a5d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + 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 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 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: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 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 + - serializedVersion: 3 + buildTarget: Server + 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: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index 099c8c6..6f99b3f 100644 --- a/README.md +++ b/README.md @@ -17,23 +17,23 @@ 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.4.9.HDRP10) for Unity 2020.3 to 2021.1 -- [**CC Unity Tools HDRP12**](https://github.com/soupday/cc_unity_tools_HDRP/releases/tag/1.4.9.HDRP12) for Unity 2021.2 and upwards +- [**CC Unity Tools HDRP10**](https://github.com/soupday/cc_unity_tools_HDRP/releases/tag/1.5.0.HDRP10) for Unity 2020.3 to 2021.1 +- [**CC Unity Tools HDRP12**](https://github.com/soupday/cc_unity_tools_HDRP/releases/tag/1.5.0.HDRP12) for Unity 2021.2 and upwards 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.4.9.URP10) for Unity 2020.3 to 2021.1 -- [**CC Unity Tools URP12**](https://github.com/soupday/cc_unity_tools_URP/releases/tag/1.4.9.URP12) for Unity 2021.2 and upwards +- [**CC Unity Tools URP10**](https://github.com/soupday/cc_unity_tools_URP/releases/tag/1.5.0.URP10) for Unity 2020.3 to 2021.1 +- [**CC Unity Tools URP12**](https://github.com/soupday/cc_unity_tools_URP/releases/tag/1.5.0.URP12) for Unity 2021.2 and upwards The main repository contains the URP10 version. See the releases page for the URP12 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.4.9) for Unity 2019.4 and upwards +- [**CC Unity Tools 3D**](https://github.com/soupday/cc_unity_tools_3D/releases/tag/1.5.0) for Unity 2019.4 and upwards How it works ============ diff --git a/Runtime/ColliderManager.cs b/Runtime/ColliderManager.cs index 338d53a..c79aaee 100644 --- a/Runtime/ColliderManager.cs +++ b/Runtime/ColliderManager.cs @@ -16,12 +16,9 @@ * along with CC_Unity_Tools. If not, see . */ -using System.Collections; using System.Collections.Generic; using UnityEngine; -using UnityEditor; using System; -using System.IO; namespace Reallusion.Import { diff --git a/Runtime/PhysicsSettingsStore.cs b/Runtime/PhysicsSettingsStore.cs index aed41b1..ea102ba 100644 --- a/Runtime/PhysicsSettingsStore.cs +++ b/Runtime/PhysicsSettingsStore.cs @@ -17,7 +17,6 @@ */ using UnityEngine; -using System.Collections; using System.Collections.Generic; using UnityEditor; using System.IO; diff --git a/Runtime/WeightMapper.cs b/Runtime/WeightMapper.cs index 6f00d09..c5c18aa 100644 --- a/Runtime/WeightMapper.cs +++ b/Runtime/WeightMapper.cs @@ -16,13 +16,11 @@ * along with CC_Unity_Tools. If not, see . */ -using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEditor; using System; -using System.IO; namespace Reallusion.Import { @@ -59,17 +57,18 @@ public class PhysicsSettings public float damping; [HideInInspector] [Range(0f, 1f)] - public float drag; + public float drag; [Range(0f, 100f)] public float stretch; [Range(0f, 100f)] public float bending; [Space(8)] public bool softRigidCollision; + [Range(0f, 100f)] public float softRigidMargin; - [HideInInspector] + [Space(8)] public bool selfCollision; - [HideInInspector] + [Range(0f, 10f)] public float selfMargin; [Space(8)] [Range(1f, 5000f)] @@ -77,7 +76,8 @@ public class PhysicsSettings [Range(1f, 500f)] public float stiffnessFrequency; [Space(8)] - [Range(0f, 1f)] + [HideInInspector] + [Range(0f, 1f)] public float colliderThreshold; public PhysicsSettings() @@ -114,8 +114,8 @@ public void Copy(PhysicsSettings p) solverFrequency = p.solverFrequency; stiffnessFrequency = p.stiffnessFrequency; colliderThreshold = p.colliderThreshold; + } } - } public PhysicsSettings[] settings; public bool updateColliders = true; @@ -127,7 +127,7 @@ public void Copy(PhysicsSettings p) public string characterGUID; public void ApplyWeightMap() - { + { GameObject clothTarget = gameObject; SkinnedMeshRenderer renderer = clothTarget.GetComponent(); if (!renderer) return; @@ -135,14 +135,14 @@ public void ApplyWeightMap() if (!mesh) return; // object scale - Vector3 objectScale = renderer.gameObject.transform.localScale; + Vector3 objectScale = renderer.gameObject.transform.localScale; float modelScale = 0.03f / (objectScale.x + objectScale.y + objectScale.z); float worldScale = (objectScale.x + objectScale.y + objectScale.z) / 3f; // add cloth component Cloth cloth = clothTarget.GetComponent(); if (!cloth) cloth = clothTarget.AddComponent(); - + // generate a mapping dictionary of cloth vertices to mesh vertices Dictionary uniqueVertices = new Dictionary(); int count = 0; @@ -154,28 +154,30 @@ public void ApplyWeightMap() { uniqueVertices.Add(hash, count++); } - } + } // fetch UV's List uvs = new List(); mesh.GetUVs(0, uvs); - + AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); + List selfCollisionIndices = new List(); List colliders = new List(); List detectedColliders = new List(colliders.Count); - ColliderManager colliderManager = gameObject.GetComponentInParent(); + ColliderManager colliderManager = gameObject.GetComponentInParent(); if (colliderManager) colliders.AddRange(colliderManager.colliders); else colliders.AddRange(gameObject.transform.parent.GetComponentsInChildren()); - + ClothSkinningCoefficient[] coefficients = new ClothSkinningCoefficient[cloth.coefficients.Length]; Array.Copy(cloth.coefficients, coefficients, coefficients.Length); // reset coefficients for (int i = 0; i < cloth.coefficients.Length; i++) { - coefficients[i].maxDistance = 0; + coefficients[i].maxDistance = 0f; + coefficients[i].collisionSphereDistance = 0f; } // apply weight maps to cloth coefficients and cloth settings @@ -193,6 +195,8 @@ public void ApplyWeightMap() { if (data.name == sourceName && data.activate) { + float rigidMargin = data.softRigidMargin * modelScale; + float selfMargin = data.selfCollision ? data.selfMargin * modelScale : 0f; cloth.useGravity = data.gravity; cloth.bendingStiffness = Mathf.Pow(1f - (data.bending / 100f), 0.5f); cloth.stretchingStiffness = Mathf.Pow(1f - (data.stretch / 100f), 0.5f); @@ -201,8 +205,8 @@ public void ApplyWeightMap() cloth.collisionMassScale = data.mass; cloth.friction = data.friction; cloth.damping = Mathf.Pow(data.damping, 0.333f); - cloth.selfCollisionDistance = data.selfMargin * modelScale; - cloth.selfCollisionStiffness = 1f; + cloth.selfCollisionDistance = USE_SELF_COLLISION ? selfMargin : 0f; + cloth.selfCollisionStiffness = 0.2f; bool doColliders = updateColliders && data.softRigidCollision; @@ -256,7 +260,7 @@ public void ApplyWeightMap() { Vector3 vert = meshVertices[vertIdx]; if (uniqueVertices.TryGetValue(SpatialHash(vert), out int clothVert)) - { + { Vector2 coord = uvs[vertIdx]; x = Mathf.Max(0, Mathf.Min(wm1, Mathf.FloorToInt(0.5f + coord.x * wm1))); y = Mathf.Max(0, Mathf.Min(hm1, Mathf.FloorToInt(0.5f + coord.y * hm1))); @@ -266,30 +270,32 @@ public void ApplyWeightMap() float maxPenetration = data.maxPenetration * weight * modelScale; float modelMax = Mathf.Max(maxDistance, maxPenetration); float worldMax = modelMax * worldScale; - if (data.softRigidCollision) + coefficients[clothVert].maxDistance = maxDistance; + coefficients[clothVert].collisionSphereDistance = maxPenetration; + + if (data.selfCollision && modelMax > selfMargin) { - coefficients[clothVert].maxDistance = maxDistance; - coefficients[clothVert].collisionSphereDistance = maxPenetration; + selfCollisionIndices.Add((uint)clothVert); } if (doColliders && optimizeColliders && - weight >= data.colliderThreshold && - modelMax > data.softRigidMargin * modelScale) + //weight >= data.colliderThreshold && + modelMax > rigidMargin) { Vector3 world = transform.localToWorldMatrix * vert; - + 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) - { + { detectedColliders.Add(cc); colliders.Remove(cc); ci--; @@ -304,10 +310,12 @@ public void ApplyWeightMap() // set coefficients if (updateConstraints) - { + { cloth.coefficients = coefficients; } + cloth.SetSelfAndInterCollisionIndices(selfCollisionIndices); + // set colliders if (updateColliders) { @@ -321,21 +329,32 @@ public void ApplyWeightMap() } cloth.capsuleColliders = detectedCapsuleColliders.ToArray(); } - } + } private static long SpatialHash(Vector3 v) { const long p1 = 73868489; const long p2 = 23875351; const long p3 = 53885459; - const long discrete = 1000000; + const long discrete = 10000000; long x = (long)(v.x * discrete); long y = (long)(v.y * discrete); long z = (long)(v.z * discrete); return (x * p1) ^ (y * p2) ^ (z * p3); - } + } + + private static bool USE_SELF_COLLISION + { + get + { + if (EditorPrefs.HasKey("RL_Importer_Use_Self_Collision")) + return EditorPrefs.GetBool("RL_Importer_Use_Self_Collision"); + return false; + } + } + #endif } } \ No newline at end of file diff --git a/Runtime/WrinkleManager.cs b/Runtime/WrinkleManager.cs index 236fc4b..ff03d42 100644 --- a/Runtime/WrinkleManager.cs +++ b/Runtime/WrinkleManager.cs @@ -16,7 +16,6 @@ * along with CC_Unity_Tools. If not, see . */ -using System.Collections; using System.Collections.Generic; using UnityEngine; diff --git a/Textures/RL_SkinMicroCavityMap.png b/Textures/RL_SkinMicroCavityMap.png new file mode 100644 index 0000000..b454136 Binary files /dev/null and b/Textures/RL_SkinMicroCavityMap.png differ diff --git a/Textures/RL_SkinMicroCavityMap.png.meta b/Textures/RL_SkinMicroCavityMap.png.meta new file mode 100644 index 0000000..5e8a4a6 --- /dev/null +++ b/Textures/RL_SkinMicroCavityMap.png.meta @@ -0,0 +1,109 @@ +fileFormatVersion: 2 +guid: 87f14e2b52f83e646a8168875ec6afb5 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 0 + 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 + vTOnly: 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: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 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/Textures/RL_SkinSpecDetail.png b/Textures/RL_SkinSpecDetail.png new file mode 100644 index 0000000..83d565e Binary files /dev/null and b/Textures/RL_SkinSpecDetail.png differ diff --git a/Textures/RL_SkinSpecDetail.png.meta b/Textures/RL_SkinSpecDetail.png.meta new file mode 100644 index 0000000..8cee971 --- /dev/null +++ b/Textures/RL_SkinSpecDetail.png.meta @@ -0,0 +1,109 @@ +fileFormatVersion: 2 +guid: 725163539542b7143948df1f4ebb4df3 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 0 + 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 + vTOnly: 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: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 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/package.json b/package.json index aa66e5b..cc30739 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.soupday.cc3_unity_tools", - "version": "1.4.9", + "version": "1.5.0", "displayName": "CC/iC Unity Tools 3D", "description": "Unity importer for Character Creator 3 & 4 and iClone 7 and 8.", "unity": "2019.4",