Add RekornTools

This commit is contained in:
우산하 2025-02-15 18:50:31 +09:00
parent 9eb8736eee
commit 5c3e71a420
250 changed files with 14833 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3305beb2138e64147a9128f10533c35e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

8
Assets/RekornTools.meta generated Normal file
View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 18de0e02918b4b7429642f58b19d1441
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/RekornTools/Avatar.meta generated Normal file
View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dcb027a5c532a6a4e98a7c28a182f831
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1de2ade747570954880c52da75ff2c6f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f37a7a5cf7195f74e97b23ed35b6ff58
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cef04c0973a08b8478649cd5978e0c78
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,74 @@
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using static UnityEditor.EditorGUILayout;
using static RekornTools.Avatar.Editor.EditorGUILayoutExtensions;
namespace RekornTools.Avatar.Editor
{
public sealed class LightEditor : BaseEditorWindow<LightEditor>
{
[SerializeField] bool _ignoreLighting;
[SerializeField] Light _sun;
[SerializeField] LightScenario _scenario;
[MenuItem("Tools/Rekorn Tools/Light Editor")]
static void OnWindowOpen() =>
GetWindow<LightEditor>("Light Editor")?.Show();
protected override void Enable()
{
EditorSceneManager.activeSceneChangedInEditMode += OnSceneChanged;
FindSun();
}
protected override void Disable()
{
EditorSceneManager.activeSceneChangedInEditMode -= OnSceneChanged;
}
void OnSceneChanged(Scene prev, Scene current) => FindSun();
void FindSun()
{
const string sunName = "Directional Light";
_sun = GameObject.Find(sunName)?.GetComponent<Light>();
if (_sun == null) _sun = new GameObject(sunName).AddComponent<Light>();
}
protected override void Draw()
{
LabelField("Light Preset", EditorStyles.boldLabel);
_ignoreLighting = Toggle("Ignore Lighting", _ignoreLighting);
SetCurrentSceneViewLighting(!_ignoreLighting);
_scenario = ObjectField("Scenario", _scenario, false);
DrawLightScenario();
}
static void SetCurrentSceneViewLighting(bool isEnable)
{
var sceneViews = SceneView.sceneViews;
if (sceneViews == null) return;
foreach (SceneView view in sceneViews)
{
if (view == null) continue;
view.sceneLighting = isEnable;
view.Repaint();
}
}
void DrawLightScenario()
{
if (_scenario == null) return;
HorizontalLine();
_scenario.DrawEditor(_ignoreLighting);
_scenario.Apply(_sun);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eb83d4315df64c9b9fadb3db1672a1b1
timeCreated: 1648893669

View file

@ -0,0 +1,56 @@
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.Rendering;
namespace RekornTools.Avatar.Editor
{
[CreateAssetMenu(menuName = "Rekorn Tools/Light Scenario")]
public sealed partial class LightScenario : ScriptableObject, IValidate
{
[SerializeField] Material Skybox;
[SerializeField] bool UseShadow;
[SerializeField] float ReflectionIntensity;
[SerializeField] Vector3 SunDirection;
[SerializeField] bool SyncColor;
[SerializeField] Color SunColor;
[SerializeField] float SunIntensity;
[SerializeField] bool UseSkyboxColor;
[SerializeField] Color SkyColor;
[SerializeField] float SkyboxIntensity;
AmbientMode AmbientMode => UseSkyboxColor && !SyncColor ? AmbientMode.Skybox : AmbientMode.Flat;
public void OnValidate()
{
var direction = SunDirection;
direction.x = Mathf.Clamp(direction.x, -1f, 1f);
direction.y = Mathf.Clamp(direction.y, -1f, 1f);
direction.z = Mathf.Clamp(direction.z, -1f, 1f);
if (direction == Vector3.zero) direction = -Vector3.up;
SunDirection = direction;
}
internal void Apply([CanBeNull] Light light)
{
RenderSettings.skybox = Skybox;
RenderSettings.reflectionIntensity = ReflectionIntensity;
RenderSettings.ambientMode = AmbientMode;
RenderSettings.ambientIntensity = SkyboxIntensity;
RenderSettings.ambientLight = (SyncColor ? SunColor : SkyColor) * SkyboxIntensity;
RenderSettings.sun = light;
if (light != null)
{
light.gameObject.SetActive(true);
light.shadowStrength = 1f;
light.shadows = UseShadow ? LightShadows.Soft : LightShadows.None;
light.transform.rotation = Quaternion.LookRotation(SunDirection);
light.color = SunColor;
light.intensity = SunIntensity;
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 271094caa09e4937b2690fdce7b07442
timeCreated: 1647705266

View file

@ -0,0 +1,55 @@
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
using static RekornTools.Avatar.Editor.EditorGUILayoutExtensions;
namespace RekornTools.Avatar.Editor
{
public sealed partial class LightScenario
{
[CustomEditor(typeof(LightScenario))]
sealed class LightScenarioDrawer : BaseEditor<LightScenario>
{
protected override void Draw(LightScenario t)
{
LabelField("Light Scenario", EditorStyles.boldLabel);
t.Skybox = ObjectField("Skybox", t.Skybox, false);
t.UseShadow = Toggle("Use Shadow", t.UseShadow);
t.ReflectionIntensity = Slider("Reflection Intensity", t.ReflectionIntensity, 0, 1);
t.SunDirection = Vector3Field("Sun Direction", t.SunDirection);
HorizontalLine();
BeginHorizontal();
{
LabelField("Sun Light", GUILayout.Width(100));
t.SyncColor = Toggle(t.SyncColor, GUILayout.Width(20));
t.SunColor = ColorField(t.SunColor, GUILayout.Width(60));
t.SunIntensity = Slider(t.SunIntensity, 0f, 8f);
}
EndHorizontal();
BeginHorizontal();
{
LabelField("Ambient Light", GUILayout.Width(100));
if (t.SyncColor)
{
LabelField("Color Synced", EditorStyles.helpBox, GUILayout.Width(80));
}
else
{
t.UseSkyboxColor = Toggle(t.UseSkyboxColor, GUILayout.Width(20));
if (t.UseSkyboxColor) LabelField("Skybox", EditorStyles.helpBox, GUILayout.Width(60));
else t.SkyColor = ColorField(t.SkyColor, GUILayout.Width(60));
}
t.SkyboxIntensity = Slider(t.SkyboxIntensity, 0f, 8f);
}
EndHorizontal();
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f03d24aafb5a4791af11e953c3b57ed1
timeCreated: 1647701543

View file

@ -0,0 +1,3 @@
{
"reference": "GUID:3b7023146530b954c921ee55ab65dbff"
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 30a3de862c721a74b9c29fbf15c0e4c2
AssemblyDefinitionReferenceImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2fb37031fdf826a42bf75fbf5585f804
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,154 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar
{
public enum DresserMode
{
DirectTransform,
ParentConstraint,
WeightTransfer,
}
[ExecuteInEditMode]
public sealed class AvatarDresser : MonoBehaviour
{
[SerializeField] DresserMode _dresserMode = DresserMode.DirectTransform;
[Header("Avatar Settings")]
[SerializeField] Animator _avatar;
[Header("Cloth Settings")]
[SerializeField] string _clothPrefix;
[SerializeField] string _clothSuffix;
[SerializeField] Transform _clothRoot;
[SerializeField] Transform _clothArmature;
[Header("Bone Exclusions")]
[SerializeField] [NotNull] BonePairs _boneExclusions = new BonePairs();
void OnValidate()
{
if (_clothRoot != null && _clothArmature == null) _clothArmature = _clothRoot.Find("Armature");
}
[Button]
public void Apply()
{
switch (_dresserMode)
{
case DresserMode.DirectTransform:
ApplyDirectTransform();
break;
case DresserMode.ParentConstraint:
ApplyParentConstraint();
break;
case DresserMode.WeightTransfer:
ApplyWeightTransfer();
break;
}
}
void ApplyDirectTransform()
{
if (_avatar == null || _clothRoot == null)
{
this.ShowConfirmDialog("Avatar or cloth is not set.");
return;
}
if (_clothArmature == null)
{
this.ShowConfirmDialog("Cloth armature is not set.");
return;
}
_clothRoot.gameObject.UnpackPrefab();
var bones = _clothArmature.GetComponentsInChildren<Transform>();
var meshes = _clothRoot.GetComponentsInChildren<SkinnedMeshRenderer>();
if (bones == null || meshes == null)
{
this.ShowConfirmDialog("No bones or meshes found.");
Undo.PerformUndo();
return;
}
var result = CreateMeshBonePairs(_clothRoot.name, transform);
result.Bones.AddRange(bones);
result.Meshes.AddRange(meshes);
TransferBones(bones, _avatar.transform);
TransferExcludedBones(_boneExclusions);
RenameBones(bones, _clothPrefix, _clothSuffix);
TransferMeshes(_clothRoot, _avatar.transform);
}
void ApplyParentConstraint()
{
this.ShowConfirmDialog("ParentConstraint is not implemented yet.");
}
void ApplyWeightTransfer()
{
this.ShowConfirmDialog("WeightTransfer is not implemented yet.");
}
[NotNull]
static MeshBonePairs CreateMeshBonePairs([NotNull] string title, [CanBeNull] Transform parent)
{
var undo = "CreateMeshBonePairs";
{
var go = new GameObject($"[{title}]");
Undo.RegisterCreatedObjectUndo(go, undo);
Undo.SetTransformParent(go.transform, parent, undo);
return Undo.AddComponent<MeshBonePairs>(go) ?? throw new NullReferenceException(undo);
}
}
static void TransferBones([NotNull] IEnumerable<Transform> bones, [NotNull] Transform targetParent)
{
var undo = "TransferBones";
foreach (var bone in bones)
{
if (bone == null) continue;
var avatarBone = targetParent.FindRecursive(bone.name);
if (avatarBone != null) Undo.SetTransformParent(bone, avatarBone, undo);
}
}
static void TransferExcludedBones([NotNull] BonePairs boneExclusions)
{
var undo = "TransferExcludedBones";
foreach (var exclusion in boneExclusions)
{
if (exclusion == null || exclusion.Source == null) continue;
Undo.SetTransformParent(exclusion.Source, exclusion.Target, undo);
}
}
static void RenameBones([NotNull] IEnumerable<Transform> bones, [CanBeNull] string prefix, [CanBeNull] string suffix)
{
var undo = "RenameBones";
foreach (var bone in bones)
{
if (bone == null) continue;
bone.UndoableAction(undo, () => bone.name = $"{prefix}{bone.name}{suffix}");
}
}
static void TransferMeshes([NotNull] Transform source, [CanBeNull] Transform parent)
{
var undo = "TransferMeshes";
Undo.SetTransformParent(source, parent, undo);
}
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1736ac66ad9eb1445bbad42ac0db8a2b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,107 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;
namespace RekornTools.Avatar
{
[ExecuteInEditMode]
[RequireComponent(typeof(MeshBonePairs))]
public sealed class BoneFinder : MonoBehaviour
{
[field: SerializeField] [CanBeNull] public Transform MeshParent { get; set; }
[field: SerializeField] [CanBeNull] public Transform BoneParent { get; set; }
[field: SerializeField] [CanBeNull] public string MeshKeyword { get; set; }
[field: SerializeField] [CanBeNull] public string BoneKeyword { get; set; }
[CanBeNull] public SkinnedMeshRenderers Meshes => _meshBonePairs ? _meshBonePairs.Meshes : null;
[CanBeNull] public Transforms Bones => _meshBonePairs ? _meshBonePairs.Bones : null;
[CanBeNull] MeshBonePairs _meshBonePairs;
void Awake() => _meshBonePairs = GetComponent<MeshBonePairs>();
public void FindMeshesFromTargetWithKeyword()
{
if (_meshBonePairs == null) return;
_meshBonePairs.UndoableAction(() => Meshes?.Initialize(MeshParent, MeshKeyword));
if (Meshes?.Count == 0) this.ShowConfirmDialog("No objects found");
}
public void FindBonesFromTargetWithKeyword()
{
if (_meshBonePairs == null) return;
_meshBonePairs.UndoableAction(() =>
{
Bones?.Initialize(BoneParent, BoneKeyword);
Bones?.RemoveRange(Meshes?.Select(x => x == null ? null : x.transform));
Bones?.Remove(BoneParent);
});
if (Bones?.Count == 0) this.ShowConfirmDialog("No objects found");
}
public void FindBonesFromWeights()
{
if (_meshBonePairs == null) return;
if (Meshes?.Count == 0)
{
this.ShowConfirmDialog("There are no meshes to find bones from.");
return;
}
var bones = new List<Transform>();
foreach (var bone in
from m in Meshes
where m
from b in m.bones
where b != null && !bones.Contains(b)
select b)
{
bones.Add(bone);
}
_meshBonePairs.UndoableAction(() => Bones?.Initialize(bones));
if (Bones?.Count == 0)
this.ShowConfirmDialog("Failed to find any bones from meshes.\n" +
"You might need to check meshes is valid or bone weights are not set to zero.");
}
public void FindBonesFromWeightsRecursive()
{
FindBonesFromWeights();
if (_meshBonePairs == null || Bones == null) return;
var children = GetChildrenRecursive(Bones);
_meshBonePairs.UndoableAction(() => Bones.AddRange(children));
}
[NotNull]
static List<Transform> GetChildrenRecursive([NotNull] Transforms target)
{
var children = new List<Transform>();
foreach (var child in
from t in target
where t != null
select t.GetComponentsInChildren<Transform>(true))
{
if (child == null) continue;
children.AddRange(child.Where(c => !AlreadyInList(c)));
}
return children;
bool AlreadyInList(Transform b) => target.Contains(b) || children.Contains(b);
}
}
}
#endif

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 39ebfe4ca23f40638645de61e45056a3
timeCreated: 1647154999

View file

@ -0,0 +1,47 @@
#if UNITY_EDITOR
using JetBrains.Annotations;
using UnityEngine;
namespace RekornTools.Avatar
{
[ExecuteInEditMode]
public sealed class BoneRenamer : MonoBehaviour
{
[Header("Source")]
[SerializeField] Transform _parent;
[SerializeField] RigNamingConvention _sourceNaming = RigNamingConvention.Default;
[Header("Target")]
[SerializeField] RigNamingConvention _targetNaming = RigNamingConvention.Default;
[Header("Rename Token")]
[SerializeField] [NotNull] NamePairs _renameToken = new NamePairs();
[Header("Rename Exclusions")]
[SerializeField] [NotNull] Transforms _exclusions = new Transforms();
[Button]
public void Rename()
{
if (_parent == null) return;
_parent.InvokeRecursive(x =>
{
if (x == null || _exclusions.Contains(x)) return;
var newName = x.name;
foreach (var token in _renameToken)
{
if (string.IsNullOrWhiteSpace(token?.Source)) continue;
newName = newName.Replace(token.Source, token.Target);
}
newName = RigNamingConvention.Convert(newName, _sourceNaming, _targetNaming);
x.gameObject.UndoableAction(() => x.name = newName);
});
}
}
}
#endif

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 01a7cdc3de5c42298b1b7fab6d20dd23
timeCreated: 1652202099

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5827a1255c85cf849b44c5475b00fa3d
timeCreated: 1647270296

View file

@ -0,0 +1,64 @@
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
using static RekornTools.Avatar.Editor.EditorGUILayoutExtensions;
using static UnityEditor.EditorGUILayout;
namespace RekornTools.Avatar.Editor
{
[CustomEditor(typeof(BoneFinder))]
public sealed class BoneFinderEditor : BaseEditor<BoneFinder>
{
protected override void Draw(BoneFinder t)
{
EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth / 5;
DrawMeshFinder(t);
HorizontalLine();
if (t.Meshes?.Count > 0)
{
DrawBoneFinder(t);
HorizontalLine();
DrawWeightedBoneFinder(t);
}
}
static void DrawMeshFinder([NotNull] BoneFinder t)
{
BeginHorizontal();
{
LabelField("Mesh Finder", EditorStyles.boldLabel);
if (GUILayout.Button("Find")) t.FindMeshesFromTargetWithKeyword();
}
EndHorizontal();
t.MeshParent = ObjectField("Parent", t.MeshParent, true);
t.MeshKeyword = TextField("Keyword", t.MeshKeyword);
}
static void DrawBoneFinder([NotNull] BoneFinder t)
{
BeginHorizontal();
{
LabelField("Bone Finder", EditorStyles.boldLabel);
if (GUILayout.Button("Find")) t.FindBonesFromTargetWithKeyword();
}
EndHorizontal();
t.BoneParent = ObjectField("Parent", t.BoneParent, true);
t.BoneKeyword = TextField("Keyword", t.BoneKeyword);
}
static void DrawWeightedBoneFinder([NotNull] BoneFinder t)
{
BeginHorizontal();
{
LabelField("Bone Finder (From Weights)", EditorStyles.boldLabel);
if (GUILayout.Button("Find")) t.FindBonesFromWeights();
if (GUILayout.Button("Find Recursive")) t.FindBonesFromWeightsRecursive();
}
EndHorizontal();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c53f396d5de4481b92de12c77addb82e
timeCreated: 1647274570

View file

@ -0,0 +1,67 @@
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
using static UnityEditor.EditorGUILayout;
namespace RekornTools.Avatar.Editor
{
[CustomEditor(typeof(MeshBonePairs))]
public sealed class MeshBonePairsEditor : BaseEditor<MeshBonePairs>
{
bool _meshesFoldout = true;
bool _bonesFoldout = false;
protected override void Draw(MeshBonePairs t)
{
EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth / 5;
DrawLists();
DrawButton(t);
}
void DrawLists()
{
LabelField("Lists", EditorStyles.boldLabel);
{
_meshesFoldout = Foldout(_meshesFoldout, "Meshes");
if (_meshesFoldout) PropertyField(serializedObject.ResolveProperty(nameof(MeshBonePairs.Meshes)), true);
_bonesFoldout = Foldout(_bonesFoldout, "Bones");
if (_bonesFoldout) PropertyField(serializedObject.ResolveProperty(nameof(MeshBonePairs.Bones)), true);
}
}
static void DrawButton([NotNull] MeshBonePairs t)
{
LabelField("Actions", EditorStyles.boldLabel);
{
BeginHorizontal();
{
LabelField("Select Items", EditorStyles.miniBoldLabel);
if (GUILayout.Button("All")) t.SelectAll();
if (GUILayout.Button("Meshes")) t.SelectMeshes();
if (GUILayout.Button("Bones")) t.SelectBones();
}
EndHorizontal();
BeginHorizontal();
{
LabelField("Clear List", EditorStyles.miniBoldLabel);
if (GUILayout.Button("All")) t.ClearAll();
if (GUILayout.Button("Meshes")) t.ClearMeshes();
if (GUILayout.Button("Bones")) t.ClearBones();
}
EndHorizontal();
BeginHorizontal();
{
LabelField("Destroy Items", EditorStyles.miniBoldLabel);
if (GUILayout.Button("All")) t.DestroyAll();
if (GUILayout.Button("Meshes")) t.DestroyMeshes();
if (GUILayout.Button("Bones")) t.DestroyBones();
}
EndHorizontal();
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 400c2ba7b80440c186deb21fbfcaee86
timeCreated: 1647273214

View file

@ -0,0 +1,3 @@
{
"reference": "GUID:3b7023146530b954c921ee55ab65dbff"
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8c4c27fbd96440e4c80aeb8ee460ff77
AssemblyDefinitionReferenceImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,38 @@
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar.Editor
{
[CustomPropertyDrawer(typeof(RigPair<>), true)]
public sealed class RigExclusionDrawer : BasePropertyDrawer
{
protected override void DrawProperty(Rect rect, SerializedProperty property, GUIContent title, int indent)
{
rect.height = 0f;
var avatarBoneName = property.ResolveProperty(nameof(RigPair.Target));
var clothBoneName = property.ResolveProperty(nameof(RigPair.Source));
rect.AppendHeight(avatarBoneName);
var x = rect.x;
var width = rect.width;
rect.width = 0f;
rect.AppendWidth(width * 0.45f);
clothBoneName.PropertyField(rect);
rect.AppendWidth(width * 0.1f);
EditorGUI.LabelField(rect, "▶", new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleCenter });
rect.AppendWidth(width * 0.45f);
avatarBoneName.PropertyField(rect);
rect.x = x;
rect.width = width;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent _) =>
EditorGUIExtensions.SingleItemHeight;
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ebe222154c9041a0be3358e9d4631ba8
timeCreated: 1652015597

View file

@ -0,0 +1,58 @@
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar.Editor
{
[CustomPropertyDrawer(typeof(RigNamingConvention))]
public sealed class RigNamingConventionDrawer : BasePropertyDrawer
{
protected override void DrawProperty(Rect rect, SerializedProperty property, GUIContent _, int indent)
{
var x = rect.x;
var fullWidth = rect.width;
rect.width = 0f;
rect.height = 0f;
rect.AppendHeight(EditorGUIExtensions.SingleItemHeight);
var type = property.ResolveProperty(nameof(RigNamingConvention.ModifierPosition));
var splitter = property.ResolveProperty(nameof(RigNamingConvention.Splitter));
var left = property.ResolveProperty(nameof(RigNamingConvention.ModifierLeft));
var right = property.ResolveProperty(nameof(RigNamingConvention.ModifierRight));
DrawElement(0.3f, left);
DrawElement(0.3f, right);
DrawElement(0.2f, type);
DrawElement(0.2f, splitter);
rect.x = x;
rect.width = fullWidth;
rect.AppendHeight(EditorGUIExtensions.SingleItemHeight);
EditorGUI.HelpBox(rect, GetPreviewText(), MessageType.Info);
void DrawElement(float weight, SerializedProperty prop)
{
var padding = EditorGUIUtility.standardVerticalSpacing;
rect.AppendWidth(fullWidth * weight - padding);
prop.PropertyField(rect);
rect.AppendWidth(padding);
}
string GetPreviewText()
{
var t = type?.enumValueIndex;
var s = splitter?.stringValue;
var l = left?.stringValue;
var r = right?.stringValue;
return t == (int)ModifierPosition.Front
? $"Head / {l}{s}Arm / {r}{s}Leg / {r}{s}Leg.001"
: $"Head / Arm{s}{l} / Leg{s}{r} / Leg{s}{r}.001";
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent _) =>
EditorGUIExtensions.SingleItemHeight * 2;
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 279b4f784c124925bc94a2fd1caf4e42
timeCreated: 1652010490

View file

@ -0,0 +1,49 @@
#if UNITY_EDITOR
using System.Linq;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar
{
[ExecuteInEditMode]
public sealed class MeshBonePairs : MonoBehaviour
{
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
[field: SerializeField] [NotNull] public SkinnedMeshRenderers Meshes { get; private set; } = new SkinnedMeshRenderers();
[field: SerializeField] [NotNull] public Transforms Bones { get; private set; } = new Transforms();
// ReSharper restore AutoPropertyCanBeMadeGetOnly.Local
public void SelectAll()
{
var selection = new Object[] { };
if (Meshes.TryGetSelections(out var meshSelections))
selection = selection.Concat(meshSelections).ToArray();
if (Bones.TryGetSelections(out var boneSelections))
selection = selection.Concat(boneSelections).ToArray();
if (selection.Length > 0) Selection.objects = selection;
}
public void ClearAll()
{
ClearMeshes();
ClearBones();
}
public void DestroyAll()
{
DestroyMeshes();
DestroyBones();
}
public void SelectMeshes() => Meshes.SelectComponents();
public void SelectBones() => Bones.SelectComponents();
public void ClearMeshes() => this.UndoableAction(() => Meshes.Clear());
public void ClearBones() => this.UndoableAction(() => Bones.Clear());
public void DestroyMeshes() => this.UndoableAction(() => Meshes.DestroyItems());
public void DestroyBones() => this.UndoableAction(() => Bones.DestroyItems());
}
}
#endif

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2a8d66205c7440078d1f5d62a41e3baa
timeCreated: 1647268279

View file

@ -0,0 +1,95 @@
#if UNITY_EDITOR
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using UnityEngine;
namespace RekornTools.Avatar
{
[ExecuteInEditMode]
public sealed class MeshOptimizer : MonoBehaviour
{
[SerializeField] Transform parent;
[SerializeField] SkinnedMeshRenderers meshes = new SkinnedMeshRenderers();
[SerializeField] Transform anchorOverride;
[SerializeField] Bounds boundingBox = new Bounds(Vector3.zero, Vector3.one * 2);
[SerializeField] [HideInInspector] Transform _prevParent;
void Awake() => Refresh();
void OnValidate()
{
if (_prevParent != parent) Refresh();
}
[Button]
void Refresh()
{
_prevParent = parent;
meshes?.Initialize(parent);
}
void OnDrawGizmosSelected()
{
if (meshes == null) return;
foreach (var mesh in meshes)
{
if (mesh == null) continue;
var prevBounds = mesh.localBounds;
{
DrawBounds(mesh, Color.yellow);
mesh.localBounds = boundingBox;
RepaintRenderer(mesh);
DrawBounds(mesh, Color.green);
}
mesh.localBounds = prevBounds;
}
}
static void DrawBounds([System.Diagnostics.CodeAnalysis.NotNull] Renderer renderer, Color color)
{
var rotation = renderer.transform.rotation;
if (renderer is SkinnedMeshRenderer)
{
var transformRoot = renderer.transform.root;
if (transformRoot != null)
rotation = transformRoot.rotation;
}
var bounds = renderer.bounds;
var matrix = Matrix4x4.TRS(bounds.center, rotation, bounds.size);
Gizmos.matrix = matrix;
Gizmos.color = color;
Gizmos.DrawWireCube(Vector3.zero, Vector3.one);
}
[SuppressMessage("ReSharper", "Unity.InefficientPropertyAccess")]
static void RepaintRenderer([System.Diagnostics.CodeAnalysis.NotNull] Renderer renderer)
{
renderer.enabled = false;
renderer.enabled = true;
}
[Button]
void Optimize()
{
if (meshes == null) return;
foreach (var mesh in meshes)
{
if (mesh == null) continue;
mesh.UndoableAction(() =>
{
mesh.probeAnchor = anchorOverride;
mesh.localBounds = boundingBox;
mesh.updateWhenOffscreen = false;
mesh.skinnedMotionVectors = false;
mesh.allowOcclusionWhenDynamic = true;
});
}
}
}
}
#endif

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 25aa88844e30426d955b4cd11ffc1b85
timeCreated: 1647594398

View file

@ -0,0 +1,125 @@
using System;
using JetBrains.Annotations;
using UnityEngine;
namespace RekornTools.Avatar
{
public enum ModifierPosition
{
Front,
End,
}
public enum ModifierType
{
None,
Both,
Left,
Right,
}
[Serializable]
public struct RigNamingConvention
{
public static RigNamingConvention Default = new RigNamingConvention
{
ModifierPosition = ModifierPosition.End,
Splitter = "_",
ModifierLeft = "L",
ModifierRight = "R",
};
[field: SerializeField] public ModifierPosition ModifierPosition { get; private set; }
[field: SerializeField] [CanBeNull] public string Splitter { get; private set; }
[field: SerializeField] [CanBeNull] public string ModifierLeft { get; private set; }
[field: SerializeField] [CanBeNull] public string ModifierRight { get; private set; }
public RigNamingConvention(ModifierPosition modifierPosition, [CanBeNull] string splitter, [CanBeNull] string modifierLeft, [CanBeNull] string modifierRight)
{
ModifierPosition = modifierPosition;
Splitter = splitter;
ModifierLeft = modifierLeft;
ModifierRight = modifierRight;
}
[NotNull] public string FrontLeft => $"{ModifierLeft}{Splitter}";
[NotNull] public string FrontRight => $"{ModifierRight}{Splitter}";
[NotNull] public string EndLeft => $"{Splitter}{ModifierLeft}";
[NotNull] public string EndRight => $"{Splitter}{ModifierRight}";
public string GetModifier(ModifierType type)
{
var pos = ModifierPosition;
if (pos == ModifierPosition.Front && type == ModifierType.Left) return FrontLeft;
if (pos == ModifierPosition.Front && type == ModifierType.Right) return FrontRight;
if (pos == ModifierPosition.End && type == ModifierType.Left) return EndLeft;
if (pos == ModifierPosition.End && type == ModifierType.Right) return EndRight;
return null;
}
public ModifierType GetModifierType([NotNull] string name)
{
var isLeft = false;
var isRight = false;
if (ModifierPosition == ModifierPosition.Front)
{
isLeft = name.Contains(FrontLeft);
isRight = name.Contains(FrontRight);
}
else
{
isLeft = name.Contains(EndLeft);
isRight = name.Contains(EndRight);
}
if (isLeft && isRight) return ModifierType.Both;
else if (isLeft) return ModifierType.Left;
else if (isRight) return ModifierType.Right;
else return ModifierType.None;
}
[NotNull]
public static string Convert([NotNull] string name, RigNamingConvention src, RigNamingConvention dst)
{
var type = src.GetModifierType(name);
switch (type)
{
case ModifierType.None:
return name;
case ModifierType.Both:
Debug.LogWarning($"Bone name {name} contains both left and right modifiers. Skipping.");
return name;
case ModifierType.Left:
case ModifierType.Right:
var from = src.GetModifier(type) ?? throw new ArgumentException("Unknown modifier type.");
var to = dst.GetModifier(type) ?? throw new ArgumentException("Unknown modifier type.");
return ReplaceModifier(name, from, to, src.ModifierPosition, dst.ModifierPosition);
default:
throw new ArgumentException("Unknown modifier type.");
}
}
[NotNull]
static string ReplaceModifier([NotNull] string name, [NotNull] string from, [NotNull] string to, ModifierPosition srcPosition, ModifierPosition dstPosition)
{
if (srcPosition == dstPosition)
{
return name.Replace(from, to);
}
else
{
if (srcPosition == ModifierPosition.Front)
{
return name.Replace(from, null) + to;
}
else
{
return to + name.Replace(from, null);
}
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 14b6076337fd4c8683016eb662323a15
timeCreated: 1652010530

View file

@ -0,0 +1,14 @@
using System;
using UnityEngine;
namespace RekornTools.Avatar
{
public sealed class RigPair : RigPair<object> { }
[Serializable]
public class RigPair<T>
{
[field: SerializeField] public T Target { get; private set; }
[field: SerializeField] public T Source { get; private set; }
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f21f9635a0f244929d34173bf8bad8e0
timeCreated: 1652015576

View file

@ -0,0 +1,19 @@
using System;
using UnityEngine;
namespace RekornTools.Avatar
{
#region List
[Serializable] public sealed class SkinnedMeshRenderers : ComponentList<SkinnedMeshRenderer> { }
[Serializable] public sealed class Transforms : ComponentList<Transform> { }
[Serializable] public sealed class BonePairs : SerializedList<BonePair> { }
[Serializable] public sealed class NamePairs : SerializedList<NamePair> { }
[Serializable] public sealed class BonePair : RigPair<Transform> { }
[Serializable] public sealed class NamePair : RigPair<string> { }
#endregion // List
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 87c24b26e4ff49d19fef1a3bd6179aa3
timeCreated: 1648312351

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c74404ee945c407d89bcc4399747bca0
timeCreated: 1649570418

View file

@ -0,0 +1,62 @@
#if UNITY_EDITOR
using System.Linq;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar
{
[ExecuteInEditMode]
public sealed class AssetManager : MonoBehaviour, IValidate
{
[SerializeField] public Transform Parent;
[SerializeField] [ReadOnlyList] [NotNull] public Renderers Renderers = new Renderers();
[SerializeField] [ReadOnlyList] [NotNull] public Materials Materials = new Materials();
[SerializeField] [ReadOnlyList] [NotNull] public Shaders Shaders = new Shaders();
[SerializeField] [ReadOnlyList] [NotNull] public Textures Textures = new Textures();
[SerializeField] [HideInInspector] Transform _prevParent;
void Awake() => Refresh();
public void OnValidate()
{
if (_prevParent != Parent) Refresh();
}
[Button]
public void Refresh()
{
_prevParent = Parent;
Renderers.Initialize(Parent);
Materials.Clear();
Shaders.Clear();
Textures.Clear();
foreach (var (material, shader) in
from r in Renderers
from material in r.sharedMaterials
select (material, material.shader))
{
if (!Materials.Contains(material)) Materials.Add(material);
if (!Shaders.Contains(shader)) Shaders.Add(shader);
AppendTexturesList(material, shader);
}
}
void AppendTexturesList([CanBeNull] Material material, [CanBeNull] Shader shader)
{
if (material == null || shader == null) return;
for (var i = 0; i < ShaderUtil.GetPropertyCount(shader); i++)
{
if (ShaderUtil.GetPropertyType(shader, i) != ShaderUtil.ShaderPropertyType.TexEnv) continue;
var texture = material.GetTexture(ShaderUtil.GetPropertyName(shader, i));
if (texture != null && !Textures.Contains(texture)) Textures.Add(texture);
}
}
}
}
#endif

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9c2ae58bf68685849a8aea8c5e5de75e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 27bc343648a6c9849b4f183357667f96
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,40 @@
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
using static RekornTools.Avatar.Editor.EditorGUILayoutExtensions;
using static UnityEditor.EditorGUILayout;
namespace RekornTools.Avatar.Editor
{
[CustomEditor(typeof(AssetManager))]
public sealed class AssetManagerEditor : BaseEditor<AssetManager>
{
bool _renderersFoldout;
bool _materialsFoldout;
bool _shadersFoldout;
bool _texturesFoldout;
protected override void Draw(AssetManager t)
{
t.Parent = ObjectField("Parent", t.Parent, true);
DrawLists(ref _renderersFoldout, nameof(AssetManager.Renderers), t.Renderers);
DrawLists(ref _materialsFoldout, nameof(AssetManager.Materials), t.Materials);
DrawLists(ref _shadersFoldout, nameof(AssetManager.Shaders), t.Shaders);
DrawLists(ref _texturesFoldout, nameof(AssetManager.Textures), t.Textures);
if (GUILayout.Button("Refresh")) t.Refresh();
}
void DrawLists<T>(ref bool foldout, [NotNull] string property, [NotNull] ObjectList<T> list) where T : Object
{
BeginHorizontal();
{
foldout = Foldout(foldout, typeof(T).Name);
if (GUILayout.Button("Select All")) list.SelectComponents();
}
EndHorizontal();
if (foldout) PropertyField(serializedObject.ResolveProperty(property), true);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bc4868172d3a484d88f546bd3ddda028
timeCreated: 1651999120

View file

@ -0,0 +1,3 @@
{
"reference": "GUID:3b7023146530b954c921ee55ab65dbff"
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b97982f805b37b746876c09880c83db3
AssemblyDefinitionReferenceImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,33 @@
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar.Editor
{
[CustomPropertyDrawer(typeof(ShaderProperty))]
public sealed class ShaderPropertyDrawer : BasePropertyDrawer
{
protected override void DrawProperty(Rect rect, SerializedProperty property, GUIContent _, int __)
{
var padding = EditorGUIUtility.standardVerticalSpacing;
var fullWidth = rect.width;
rect.width = 0f;
DrawElement(0.15f, nameof(ShaderProperty.Shader));
DrawElement(0.1f, nameof(ShaderProperty.Index));
DrawElement(0.15f, nameof(ShaderProperty.Type));
DrawElement(0.4f, nameof(ShaderProperty.Name));
DrawElement(0.2f, nameof(ShaderProperty.TextureType), false);
void DrawElement(float weight, string propertyName, bool isReadOnly = true)
{
if (propertyName == null) return;
var prop = property.ResolveProperty(propertyName);
rect.AppendWidth(fullWidth * weight - padding);
prop.DisabledPropertyField(rect, isReadOnly);
rect.AppendWidth(padding);
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 24cd6b4b6ad94fcdb9b936ea07df86c4
timeCreated: 1647777014

View file

@ -0,0 +1,46 @@
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar.Editor
{
[CustomPropertyDrawer(typeof(TexturePropertiesByShader))]
public sealed class TexturePropertiesByShaderDrawer : SerializedKeyValueDrawer
{
[NotNull] readonly ReorderableListHelper _listHelper = new ReorderableListHelper(SerializedList.FieldName, true);
protected override void DrawProperty(Rect rect, SerializedProperty property, GUIContent _, int indent)
{
var helper = Helper.Update(property);
var key = helper.Key;
var value = helper.Value;
rect.ApplyIndent(++indent);
var foldoutWidth = 10f;
var keyWidth = rect.width - foldoutWidth;
rect.width = 0f;
rect.height = helper.KeyHeight;
rect.AppendWidth(foldoutWidth);
property.isExpanded = EditorGUI.Foldout(rect, property.isExpanded, GUIContent.none);
rect.AppendWidth(keyWidth);
key.DisabledPropertyField(rect);
if (property.isExpanded)
{
rect.AppendHeight(helper.ValueHeight);
_listHelper.Update(value).Draw(rect);
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent _)
{
var helper = Helper.Update(property);
return property?.isExpanded ?? false
? helper.ValueHeight
: helper.KeyHeight;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 37bf0b354c0846e39f5f593c120db2fd
timeCreated: 1651138620

View file

@ -0,0 +1,31 @@
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar.Editor
{
[CustomPropertyDrawer(typeof(TextureProperty))]
public sealed class TexturePropertyDrawer : BasePropertyDrawer
{
protected override void DrawProperty(Rect rect, SerializedProperty property, GUIContent _, int __)
{
var padding = EditorGUIUtility.standardVerticalSpacing;
var fullWidth = rect.width;
rect.width = 0f;
DrawElement(0.2f, nameof(ShaderProperty.Index));
DrawElement(0.6f, nameof(ShaderProperty.Name));
DrawElement(0.2f, nameof(ShaderProperty.TextureType), false);
void DrawElement(float weight, string propertyName, bool isReadOnly = true)
{
if (propertyName == null) return;
var prop = property.ResolveProperty(propertyName);
rect.AppendWidth(fullWidth * weight - padding);
prop.DisabledPropertyField(rect, isReadOnly);
rect.AppendWidth(padding);
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c89824b979c0494ca396f2f9255207d0
timeCreated: 1651137470

View file

@ -0,0 +1,50 @@
using System;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.Rendering;
namespace RekornTools.Avatar
{
[Serializable]
public class TextureProperty : ShaderProperty
{
public TextureProperty([NotNull] Shader shader, int index, ShaderPropertyType type, [NotNull] string name) : base(shader, index, type, name) { }
public TextureProperty([NotNull] Shader shader, int index) : base(shader, index) { }
}
[Serializable]
public class ShaderProperty
{
[field: SerializeField] public Shader Shader { get; private set; }
[field: SerializeField] public int Index { get; private set; }
[field: SerializeField] public ShaderPropertyType Type { get; private set; }
[field: SerializeField] public string Name { get; private set; }
[field: SerializeField] public TextureType TextureType { get; set; }
public ShaderProperty([NotNull] Shader shader, int index, ShaderPropertyType type, [NotNull] string name)
{
Shader = shader;
Index = index;
Type = type;
Name = name;
if (type == ShaderPropertyType.Texture) TextureType = AssumeTextureType(name);
}
public ShaderProperty([NotNull] Shader shader, int index) :
this(shader, index, shader.GetPropertyType(index), shader.GetPropertyName(index) ?? "null") { }
static TextureType AssumeTextureType([NotNull] string name)
{
var token = name.ToLower();
if (token.Contains("normal")) return TextureType.Normal;
if (token.Contains("emissi")) return TextureType.Emissive;
if (token.Contains("sample") || token.Contains("pos")) return TextureType.Sampler;
if (token.Contains("mask") || token.Contains("map")) return TextureType.Mask;
return TextureType.Default;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6377e8b9104b46b8b6ce76e326d14812
timeCreated: 1647783353

View file

@ -0,0 +1,164 @@
#if UNITY_EDITOR
using System.Linq;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar
{
[ExecuteInEditMode]
public sealed class TextureOptimizer : MonoBehaviour
{
[Header("Target")]
[SerializeField] Transform _parent;
[SerializeField] [NotNull] Renderers _meshes = new Renderers();
[Header("Textures")]
[SerializeField] TexturePropertiesTable _propertiesTable;
[SerializeField] [ReadOnlyList] [ItemNotSpan] [NotNull] TexturesMapByType _texturesMap = new TexturesMapByType();
[Header("Optimizer")]
[SerializeField] TextureOptimizerSettings _optimizerSettings;
[SerializeField] [HideInInspector] Transform _prevParent;
[SerializeField] [HideInInspector] TexturePropertiesTable _prevTable;
void Awake() => _meshes.Initialize(_parent);
void OnValidate()
{
if (_prevParent != _parent || _prevTable != _propertiesTable) Refresh();
}
[Button]
void Refresh()
{
_prevParent = _parent;
_meshes.Initialize(_parent);
_prevTable = _propertiesTable;
ResetTexturesMap();
AssignTexturesMap();
}
void ResetTexturesMap()
{
_texturesMap.Clear();
_texturesMap.MatchDictionaryKey(_ => new Textures());
_texturesMap.Remove(TextureType.Ignore);
}
void AssignTexturesMap()
{
if (_propertiesTable == null) return;
foreach (var properties in _propertiesTable.TexturePropertiesMap.Values)
{
if (properties == null) continue;
foreach (var property in properties)
{
if (property == null) continue;
AssignTextureProperty(property);
}
}
}
void AssignTextureProperty([NotNull] ShaderProperty property)
{
var type = property.TextureType;
if (type == TextureType.Ignore) return;
_texturesMap[type]?.AddRange(GetTextureList(property));
}
[CanBeNull]
Textures GetTextureList([NotNull] ShaderProperty property)
{
var list = new Textures();
foreach (var mesh in _meshes)
{
if (mesh == null) continue;
AddTextures(mesh.sharedMaterials);
}
return list.Count == 0 ? null : list;
void AddTextures(Material[] materials)
{
if (materials == null) return;
foreach (var material in materials)
if (TryFindMatchingTexture(material, property, out var texture))
AppendTexture(texture);
}
void AppendTexture(Texture texture)
{
if (!list.Contains(texture)) list.Add(texture);
}
}
bool TryFindMatchingTexture([CanBeNull] Material material, [NotNull] ShaderProperty property, [CanBeNull] out Texture texture)
{
texture = null;
if (material == null) return false;
if (material.shader != property.Shader) return false;
if (!material.HasProperty(property.Name)) return false;
texture = material.GetTexture(property.Name);
if (texture == null) return false;
if (IsTextureExist(texture)) return false;
return true;
}
bool IsTextureExist([NotNull] Texture texture) =>
_texturesMap.Any(x => x.Value?.Contains(texture) == true);
[Button]
void Optimize()
{
if (_propertiesTable != null) _propertiesTable.UpdateTable();
if (_optimizerSettings == null)
{
this.ShowConfirmDialog("You need to set the optimizer settings first.");
return;
}
if (!EditorUtility.DisplayDialog("Warning"
, "This operation will reimport all textures from list. "
+ "This operation can't be undone, and takes huge amount of time."
, "Proceed"
, "Abort")) return;
ApplyPreset(_texturesMap, _optimizerSettings.PresetMap);
}
void ApplyPreset([NotNull] TexturesMapByType texturesMapByType, [NotNull] TexturePresetMapByType presets)
{
var count = 0;
var prevSize = new AssetSize(0L, 0L);
var newSize = new AssetSize(0L, 0L);
foreach (var map in texturesMapByType)
{
if (!presets.TryGetValue(map.Key, out var preset)) continue;
if (preset == null) continue;
if (map.Value == null) continue;
foreach (var texture in map.Value)
{
count++;
prevSize += AssetSize.GetAssetSize(texture);
AssetHelper.ApplyPreset(texture, preset);
newSize += AssetSize.GetAssetSize(texture);
}
}
var savedSize = newSize - prevSize;
this.ShowConfirmDialog($"Total {count} textures optimized.\n"
+ $"[Before] {prevSize.ToString()}\n"
+ $"[After] {newSize.ToString()}\n"
+ $"[Saved] {savedSize.ToString()}\n");
}
}
}
#endif

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8b3cf9caebe14c7a815cfbf232f86cdd
timeCreated: 1647707466

View file

@ -0,0 +1,43 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor;
using UnityEditor.Presets;
using UnityEngine;
namespace RekornTools.Avatar
{
[CreateAssetMenu(menuName = "Rekorn Tools/Texture Optimizer Settings")]
public sealed class TextureOptimizerSettings : ScriptableObject, IValidate
{
[SerializeField] [ReadOnlyList] [NotNull]
public TexturePresetMapByType PresetMap = new TexturePresetMapByType();
[NotNull] readonly IReadOnlyCollection<TextureType> _types = DictionaryExtensions.GetKeys<TextureType>();
public void OnEnable() => UpdateTable();
public void OnValidate() => ClearInvalidPreset();
void UpdateTable()
{
PresetMap.MatchDictionaryKey(_ => default);
PresetMap.Remove(TextureType.Ignore);
}
void ClearInvalidPreset()
{
foreach (var type in _types)
{
if (type == TextureType.Ignore) continue;
if (!PresetMap.TryGetValue(type, out var preset)) continue;
if (preset == null) continue;
if (!IsPresetTypeValid(preset, $"{nameof(UnityEditor)}.{nameof(TextureImporter)}"))
PresetMap[type] = null;
}
}
static bool IsPresetTypeValid([NotNull] Preset preset, [NotNull] string name) =>
preset.GetPresetType().GetManagedTypeName()?.Equals(name) ?? false;
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a3f4698d610241f38d8d77c8b50f9520
timeCreated: 1647969551

View file

@ -0,0 +1,51 @@
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;
using UnityEngine.Rendering;
namespace RekornTools.Avatar
{
[CreateAssetMenu(menuName = "Rekorn Tools/Texture Properties Table")]
public sealed class TexturePropertiesTable : ScriptableObject
{
[SerializeField] [ReadOnlyList] [ItemNotSpan] [NotNull]
public TexturePropertiesMapByShader TexturePropertiesMap = new TexturePropertiesMapByShader();
public void OnEnable() => UpdateTable();
[Button]
public void UpdateTable()
{
var shaders = ShaderPropertyExtensions.AllUserShadersInProject?.ToList();
if (shaders == null) return;
TexturePropertiesMap.MatchDictionaryKey(shaders, GetTexturePropertyList);
}
[Button]
public void ResetTable()
{
TexturePropertiesMap.Clear();
UpdateTable();
}
[CanBeNull]
public static TextureProperties GetTexturePropertyList([CanBeNull] Shader shader)
{
if (shader == null) return null;
var count = shader.GetPropertyCount();
if (count == 0) return null;
var properties = new TextureProperties();
for (var i = 0; i < count; i++)
{
if (shader.GetPropertyFlags(i) == ShaderPropertyFlags.HideInInspector) continue;
if (shader.GetPropertyType(i) != ShaderPropertyType.Texture) continue;
properties.Add(new TextureProperty(shader, i));
}
return properties.Count == 0 ? null : properties;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 89d3ec4237704783bffc3ea47b9602ba
timeCreated: 1649259700

View file

@ -0,0 +1,15 @@
namespace RekornTools.Avatar
{
public enum TextureType
{
Ignore,
Default,
Opaque,
Alpha,
Emissive,
Normal,
Mask,
Sampler,
Icon,
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a195c1996f2d4f5fa94918055616049d
timeCreated: 1647969618

View file

@ -0,0 +1,38 @@
using System;
using UnityEditor.Presets;
using UnityEngine;
namespace RekornTools.Avatar
{
#region BasicList
[Serializable] public sealed class Materials : ObjectList<Material> { }
[Serializable] public sealed class Shaders : ObjectList<Shader> { }
[Serializable] public sealed class Textures : ObjectList<Texture> { }
#endregion // BasicList
#region List
[Serializable] public sealed class Renderers : ComponentList<Renderer> { }
[Serializable] public sealed class TextureProperties : SerializedList<TextureProperty> { }
[Serializable] public sealed class ShaderProperties : SerializedList<ShaderProperty> { }
#endregion // List
#region Dictionary
[Serializable] public sealed class TexturePresetMapByType : SerializedDictionary<TexturePresetByType, TextureType, Preset> { }
[Serializable] public sealed class TexturesMapByType : SerializedDictionary<TexturesByType, TextureType, Textures> { }
[Serializable] public sealed class TexturePropertiesMapByShader : SerializedDictionary<TexturePropertiesByShader, Shader, TextureProperties> { }
#endregion // Dictionary
#region KeyValue
[Serializable] public sealed class TexturePresetByType : HorizontalKeyValue<TextureType, Preset> { }
[Serializable] public sealed class TexturesByType : HorizontalKeyValue<TextureType, Textures> { }
[Serializable] public sealed class TexturePropertiesByShader : SerializedKeyValue<Shader, TextureProperties> { }
#endregion // KeyValue
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 23146dc6476945f5a8e68d26668fb2ab
timeCreated: 1649590899

8
Assets/RekornTools/Avatar/Core.meta generated Normal file
View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 29a60d6815d0ef34cb350ccb2de0dd06
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f1d673a55cf464346bbd91d1668d7408
timeCreated: 1647965920

View file

@ -0,0 +1,7 @@
using System;
namespace RekornTools.Avatar
{
[AttributeUsage(AttributeTargets.Method)]
public sealed class ButtonAttribute : Attribute { }
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4b1f8999d73645448c2b92698a946981
timeCreated: 1651836246

View file

@ -0,0 +1,7 @@
using System;
namespace RekornTools.Avatar
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class ItemNotSpanAttribute : Attribute { }
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f59b59bc60b84f45994be7a7a285252d
timeCreated: 1647965702

View file

@ -0,0 +1,7 @@
using System;
namespace RekornTools.Avatar
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class ReadOnlyListAttribute : Attribute { }
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 18838e3ca6514e5b8572f939b2f476d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar
{
[System.Serializable]
public class ComponentList<T> : ObjectList<T> where T : Component
{
void ShowDialog([NotNull] string message)
{
var header = $"[{nameof(ObjectList<T>)}<{typeof(T).Name}>]";
Debug.LogWarning($"{header} {message}");
EditorUtility.DisplayDialog(header, message, "Confirm");
}
#region UnityObject
public void DestroyItems()
{
var destroyTarget = new List<T>();
var destroyFailed = new StringBuilder();
foreach (var o in this)
{
if (o == null)
{
// ReSharper disable once ExpressionIsAlwaysNull
destroyTarget.Add(o);
}
else if (!IsObjectPrefab(o))
{
Undo.DestroyObjectImmediate(o.gameObject);
destroyTarget.Add(o);
}
else
{
destroyFailed.Append($"{o.name}, ");
}
}
if (destroyFailed.Length > 0)
{
var objectsList = destroyFailed.ToString().TrimEnd(',', ' ');
ShowDialog($"Failed to destroy following objects: {objectsList}\n" +
"You might need to unpack prefabs before destroy them.");
}
RemoveRange(destroyTarget);
}
static bool IsObjectPrefab([NotNull] Object o) =>
o && PrefabUtility.GetPrefabInstanceStatus(o) == PrefabInstanceStatus.Connected;
public void Initialize([CanBeNull] Transform parent, [CanBeNull] string keyword = null)
{
var objects = parent == null
? GameObjectExtensions.GetAllGameObjectsInScene?.SelectMany(x => x == null ? null : x.GetComponents<T>())
: parent.GetComponentsInChildren<T>(true);
if (keyword != null && !string.IsNullOrWhiteSpace(keyword))
objects = objects?.Where(x => x != null && x.name.Contains(keyword));
Initialize(objects?.Where(x => x));
}
#endregion // UnityObject
#region Selection
public override bool TryGetSelections(out Object[] selections)
{
selections = this.Select(x => x == null ? null : x.gameObject as Object).ToArray();
return selections.Length != 0;
}
#endregion // Selection
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fe3d0359e861479cb02ecd2c47cca64a
timeCreated: 1647187947

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e79b2b7c27306924aa13cd7dc57fbee0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
namespace RekornTools.Avatar
{
public static class DictionaryExtensions
{
public static void MatchDictionaryKey<T, V>([NotNull] this Dictionary<T, V> target, [NotNull] Func<T, V> getValue) where T : Enum =>
target.MatchDictionaryKey(GetKeys<T>(), getValue);
public static void MatchDictionaryKey<T, V>([NotNull] this Dictionary<T, V> target, [NotNull] IReadOnlyCollection<T> types, [NotNull] Func<T, V> getValue)
{
var keys = target.Keys.ToList();
var toAdd = types.Except(keys).ToList();
var toRemove = keys.Except(types).ToList();
foreach (var key in toAdd) target.Add(key, getValue.Invoke(key));
foreach (var key in toRemove) target.Remove(key);
}
[NotNull] public static IReadOnlyCollection<T> GetKeys<T>() where T : Enum =>
Enum.GetValues(typeof(T)).Cast<T>().ToList();
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b07bd8be77b54b4cbe90d413eab28d03
timeCreated: 1651155774

View file

@ -0,0 +1,36 @@
using System;
using JetBrains.Annotations;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif // UNITY_EDITOR
namespace RekornTools.Avatar
{
public static class EditorExtensions
{
public static void ShowConfirmDialog<T>([NotNull] this T script, [NotNull] string message) where T : MonoBehaviour
{
var header = $"[{typeof(T)}({script.gameObject.name})]";
Debug.LogWarning($"{header} {message}");
#if UNITY_EDITOR
EditorUtility.DisplayDialog(header, message, "Confirm");
#endif // UNITY_EDITOR
}
public static void UndoableAction([NotNull] this Object target, [NotNull] Action action) =>
UndoableAction(target, target.name, action);
public static void UndoableAction([NotNull] this Object target, [NotNull] string actionName, [NotNull] Action action)
{
#if UNITY_EDITOR
Undo.RegisterCompleteObjectUndo(target, actionName);
action();
Undo.FlushUndoRecordObjects();
#endif // UNITY_EDITOR
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c24484bfde984bc184d95ce33634e57b
timeCreated: 1649607325

View file

@ -0,0 +1,50 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace RekornTools.Avatar
{
public static class GameObjectExtensions
{
public static void BackupGameObject([NotNull] this Object obj)
{
var backup = Object.Instantiate(obj);
Undo.RegisterCreatedObjectUndo(backup, "Backup Cloth");
}
public static void UnpackPrefab([CanBeNull] this GameObject prefab)
{
if (PrefabUtility.GetPrefabInstanceStatus(prefab) != PrefabInstanceStatus.Connected) return;
var target = PrefabUtility.GetOutermostPrefabInstanceRoot(prefab);
PrefabUtility.UnpackPrefabInstance(target, PrefabUnpackMode.OutermostRoot, InteractionMode.UserAction);
}
[CanBeNull]
public static IEnumerable<GameObject> AllGameObjectsInProject =>
Resources.FindObjectsOfTypeAll(typeof(GameObject)) as GameObject[];
[CanBeNull]
public static IEnumerable<GameObject> GetAllGameObjectsInScene =>
AllGameObjectsInProject?.Where(IsEditableSceneObject);
static bool IsEditableSceneObject([CanBeNull] GameObject go)
{
if (go == null) return false;
var root = go.transform.root;
if (root == null) root = go.transform;
#if UNITY_EDITOR
var isStoredOnDisk = EditorUtility.IsPersistent(root);
#else
var isStoredOnDisk = false;
#endif
return !isStoredOnDisk && !(root.hideFlags == HideFlags.NotEditable || root.hideFlags == HideFlags.HideAndDontSave);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9c43c7164e54445c8b4ac98ae09d0b19
timeCreated: 1647276663

View file

@ -0,0 +1,61 @@
using System;
using System.Reflection;
using System.Text;
using JetBrains.Annotations;
namespace RekornTools.Avatar
{
public static class ReflectionExtensions
{
#region Attribute
public static readonly BindingFlags Everything = ~BindingFlags.Default;
public static (MemberInfo, Type) GetFieldOrProperty([CanBeNull] this Type type, [CanBeNull] string name)
{
if (string.IsNullOrWhiteSpace(name)) return (null, type);
var fieldInfo = type?.GetField(name, Everything);
if (fieldInfo != null) return (fieldInfo, fieldInfo.FieldType);
var propertyInfo = type?.GetProperty(name, Everything);
if (propertyInfo != null) return (propertyInfo, propertyInfo.PropertyType);
return (null, type);
}
#endregion // Attribute
#region Property
[NotNull] static readonly StringBuilder Sb = new StringBuilder();
[NotNull] const string AutoPropertyHeader = "<";
[NotNull] const string AutoPropertyFooter = ">k__BackingField";
public static bool IsAutoProperty([NotNull] string name) =>
name.StartsWith(AutoPropertyHeader, StringComparison.Ordinal)
&& name.EndsWith(AutoPropertyFooter, StringComparison.Ordinal);
[NotNull]
public static string ResolveDisplayName([NotNull] string name)
{
if (!IsAutoProperty(name)) return name;
name = name.Remove(0, AutoPropertyHeader.Length);
name = name.Remove(name.Length - AutoPropertyFooter.Length, AutoPropertyFooter.Length);
return name;
}
[NotNull]
public static string ResolveFieldName([NotNull] string name)
{
if (IsAutoProperty(name)) return name;
Sb.Clear();
Sb.Append(AutoPropertyHeader);
Sb.Append(name);
Sb.Append(AutoPropertyFooter);
return Sb.ToString();
}
#endregion // Property
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b90ac13fa7054288ac26ae16cfe31ca9
timeCreated: 1651847493

View file

@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace RekornTools.Avatar
{
public static class ShaderPropertyExtensions
{
[CanBeNull]
public static IEnumerable<Shader> AllUserShadersInProject =>
from material in AllMaterialsInProject
let shader = material.shader
where shader != null
select shader;
[CanBeNull]
public static IEnumerable<Material> AllMaterialsInProject =>
#if UNITY_EDITOR
from guid in AssetDatabase.FindAssets("t:Material")
let path = AssetDatabase.GUIDToAssetPath(guid)
let material = AssetDatabase.LoadAssetAtPath<Material>(path)
where material != null
select material;
#else
null;
#endif
[CanBeNull]
public static IEnumerable<Shader> AllShadersInProject =>
#if UNITY_EDITOR
from guid in AssetDatabase.FindAssets("t:Shader")
let path = AssetDatabase.GUIDToAssetPath(guid)
let shader = AssetDatabase.LoadAssetAtPath<Shader>(path)
where shader != null
select shader;
#else
null;
#endif
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0007637d95157434a824ad5122f62b1c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,68 @@
using System;
using JetBrains.Annotations;
using UnityEngine;
namespace RekornTools.Avatar
{
public static class TransformExtensions
{
public static void InvokeRecursive([NotNull] this Transform parent, [NotNull] Action<Transform> action)
{
action.Invoke(parent);
foreach (Transform child in parent)
{
if (child == null) continue;
child.InvokeRecursive(action);
}
}
[CanBeNull]
public static Transform FindRecursive([NotNull] this Transform parent, [NotNull] string name)
{
if (parent.name == name) return parent;
foreach (Transform child in parent)
{
if (child == null) continue;
if (child.name == name) return child;
var result = child.FindRecursive(name);
if (result != null) return result;
}
return null;
}
public static Bounds TransformBounds([NotNull] this Transform transform, Bounds localBounds)
{
var center = transform.TransformPoint(localBounds.center);
var extents = localBounds.extents;
var axisX = transform.TransformVector(extents.x, 0, 0);
var axisY = transform.TransformVector(0, extents.y, 0);
var axisZ = transform.TransformVector(0, 0, extents.z);
extents.x = Mathf.Abs(axisX.x) + Mathf.Abs(axisY.x) + Mathf.Abs(axisZ.x);
extents.y = Mathf.Abs(axisX.y) + Mathf.Abs(axisY.y) + Mathf.Abs(axisZ.y);
extents.z = Mathf.Abs(axisX.z) + Mathf.Abs(axisY.z) + Mathf.Abs(axisZ.z);
return new Bounds { center = center, extents = extents };
}
public static Bounds InverseTransformBounds([NotNull] this Transform transform, Bounds localBounds)
{
var center = transform.InverseTransformPoint(localBounds.center);
var extents = localBounds.extents;
var axisX = transform.InverseTransformVector(extents.x, 0, 0);
var axisY = transform.InverseTransformVector(0, extents.y, 0);
var axisZ = transform.InverseTransformVector(0, 0, extents.z);
extents.x = Mathf.Abs(axisX.x) + Mathf.Abs(axisY.x) + Mathf.Abs(axisZ.x);
extents.y = Mathf.Abs(axisX.y) + Mathf.Abs(axisY.y) + Mathf.Abs(axisZ.y);
extents.z = Mathf.Abs(axisX.z) + Mathf.Abs(axisY.z) + Mathf.Abs(axisZ.z);
return new Bounds { center = center, extents = extents };
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 926d769f1dfc4f66b1e105bfa2d67db2
timeCreated: 1648135240

View file

@ -0,0 +1,7 @@
namespace RekornTools.Avatar
{
public interface IValidate
{
void OnValidate();
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0811a75b3d044aebae9d2b3439ccd8a5
timeCreated: 1651128798

View file

@ -0,0 +1,22 @@
using System.Linq;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
namespace RekornTools.Avatar
{
[System.Serializable]
public class ObjectList<T> : SerializedList<T> where T : Object
{
public void SelectComponents()
{
if (TryGetSelections(out var selections)) Selection.objects = selections;
}
public virtual bool TryGetSelections([NotNull] out Object[] selections)
{
selections = this.Select(x => x == null ? null : x as Object).ToArray();
return selections.Length != 0;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 40d9a2e62dd3405f85ee17be3e922a54
timeCreated: 1652002210

View file

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEngine;
namespace RekornTools.Avatar
{
public sealed class SerializedDictionary : SerializedDictionary<SerializedKeyValue, object, object>
{
[NotNull] public const string FieldName = nameof(Items);
}
[Serializable]
public class SerializedDictionary<TKeyValue, TKey, TValue> :
Dictionary<TKey, TValue>, ISerializationCallbackReceiver
where TKeyValue : SerializedKeyValue<TKey, TValue>, new()
{
[SerializeField] [NotNull] protected List<TKeyValue> Items = new List<TKeyValue>();
public void OnBeforeSerialize()
{
Items.Clear();
foreach (var item in this)
{
var pair = new TKeyValue
{
Key = item.Key,
Value = item.Value,
};
Items.Add(pair);
}
}
public void OnAfterDeserialize()
{
Clear();
foreach (var item in Items)
{
Add(item.Key, item.Value);
}
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 34bd198ed40c442bb43c305a864d753e
timeCreated: 1651120815

View file

@ -0,0 +1,24 @@
using UnityEngine;
using System;
using JetBrains.Annotations;
namespace RekornTools.Avatar
{
public sealed class SerializedKeyValue : SerializedKeyValue<object, object> { }
[Serializable] public class HorizontalKeyValue<K, V> : SerializedKeyValue<K, V> { }
[Serializable] public class SerializedKeyValue<K, V>
{
[field: SerializeField] [CanBeNull] public K Key { get; set; }
[field: SerializeField] [CanBeNull] public V Value { get; set; }
protected SerializedKeyValue() { }
protected SerializedKeyValue([CanBeNull] K key, [CanBeNull] V value)
{
Key = key;
Value = value;
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bfc26b3e497a4d4a85759ca208c7f097
timeCreated: 1647761218

Some files were not shown because too many files have changed in this diff Show more