Skip to content

Commit

Permalink
[SDK-376] Add mesh opt import (#144)
Browse files Browse the repository at this point in the history
## [SDK-376](https://ready-player-me.atlassian.net/browse/SDK-376)

## Description

-  Refactored AvatarConfigEditor with ui toolkit
-  Added importing of mesh opt compression package
  • Loading branch information
rYuuk authored Nov 3, 2023
1 parent 65ac286 commit ee4bd9e
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 147 deletions.
306 changes: 306 additions & 0 deletions Editor/UI/AvatarConfigEditor/AvatarConfigEditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

namespace ReadyPlayerMe.Core.Editor
{
[CustomEditor(typeof(AvatarConfig))]
public class AvatarConfigEditor : UnityEditor.Editor
{
private const string DIALOG_TITLE = "Read Player Me";
private const string DIALOG_MESSAGE = "Do you want to install {0} Unity Package: {1} ?";
private const string DIALOG_OK = "Ok";
private const string DIALOG_CANCEL = "Cancel";
private const string ADD_MORPH_TARGET = "Add Morph Target";
private const string DELETE_MORPH_TARGET = "Delete Morph Target";
private const string REMOVE_BUTTON_TEXT = "Remove";
private const string MESH_OPT_PACKAGE_NAME = "com.unity.meshopt.decompress";

[SerializeField] private VisualTreeAsset visualTreeAsset;

private AvatarConfig avatarConfigTarget;
private List<Label> morphTargetLabels;

private Dictionary<VisualElement, string> morphTargetsParentVisualElement;

private VisualElement selectedMorphTargets;

private SerializedProperty useDracoCompressionField;
private bool previousDracoCompressionValue;

private SerializedProperty useMeshOptCompressionField;
private bool previousMeshOptCompressionValue;

private VisualElement root;

public override VisualElement CreateInspectorGUI()
{
root = new VisualElement();
visualTreeAsset.CloneTree(root);

avatarConfigTarget = (AvatarConfig) target;

SetupLod();
SetupPose();
SetupTextureAtlas();
SetupTextureSizeLimit();
SetupUseHands();
SetupCompressionPackages();
SetupTextureChannel();
SetupMorphTargets();

return root;
}

private void SetupLod()
{
var lod = root.Q<EnumField>("Lod");
lod.SetValueWithoutNotify(avatarConfigTarget.Lod);
lod.RegisterValueChangedCallback(x =>
{
avatarConfigTarget.Lod = (Lod) x.newValue;
serializedObject.ApplyModifiedProperties();
Debug.Log("Lod changed:" + x.newValue);
}
);
}

private void SetupPose()
{
var pose = root.Q<EnumField>("Pose");
pose.SetValueWithoutNotify(avatarConfigTarget.Pose);
pose.RegisterValueChangedCallback(x =>
{
avatarConfigTarget.Pose = (Pose) x.newValue;
serializedObject.ApplyModifiedProperties();
}
);
}

private void SetupTextureAtlas()
{
var textureAtlas = root.Q<EnumField>("TextureAtlas");
textureAtlas.SetValueWithoutNotify(avatarConfigTarget.TextureAtlas);
textureAtlas.RegisterValueChangedCallback(x =>
{
avatarConfigTarget.TextureAtlas = (TextureAtlas) x.newValue;
serializedObject.ApplyModifiedProperties();
}
);
}

private void SetupTextureSizeLimit()
{
var textureSizeLimit = root.Q<SliderInt>("TextureSizeLimit");
textureSizeLimit.SetValueWithoutNotify(avatarConfigTarget.TextureSizeLimit);
textureSizeLimit.RegisterValueChangedCallback(x =>
{
avatarConfigTarget.TextureSizeLimit = x.newValue;
serializedObject.ApplyModifiedProperties();
}
);
}

private void SetupUseHands()
{
var useHands = root.Q<Toggle>("UseHands");
useHands.SetValueWithoutNotify(avatarConfigTarget.UseHands);
useHands.RegisterValueChangedCallback(x =>
{
avatarConfigTarget.UseHands = x.newValue;
serializedObject.ApplyModifiedProperties();
}
);
}

private void SetupCompressionPackages()
{
var useDracoCompression = root.Q<Toggle>("UseDracoCompression");
var useMeshOptCompression = root.Q<Toggle>("UseMeshOptCompression");

var optimizationPackages =root.Q<Foldout>("OptimizationPackages");
optimizationPackages.RegisterValueChangedCallback(x =>
{
useDracoCompression.SetValueWithoutNotify(ModuleInstaller.IsModuleInstalled(ModuleList.DracoCompression.name));
useMeshOptCompression.SetValueWithoutNotify(PackageManagerHelper.IsPackageInstalled(MESH_OPT_PACKAGE_NAME));
});

useDracoCompression.RegisterValueChangedCallback(x =>
{
avatarConfigTarget.UseDracoCompression = x.newValue;
if (ModuleInstaller.IsModuleInstalled(ModuleList.DracoCompression.name))
{
return;
}

if (EditorUtility.DisplayDialog(
DIALOG_TITLE,
string.Format(DIALOG_MESSAGE, "Draco compression", ModuleList.DracoCompression.name),
DIALOG_OK,
DIALOG_CANCEL))
{
ModuleInstaller.AddModuleRequest(ModuleList.DracoCompression.Identifier);
}
else
{
avatarConfigTarget.UseDracoCompression = false;
useDracoCompression.SetValueWithoutNotify(false);
}

serializedObject.ApplyModifiedProperties();
}
);

useMeshOptCompression.RegisterValueChangedCallback(x =>
{
avatarConfigTarget.UseMeshOptCompression = x.newValue;
if (PackageManagerHelper.IsPackageInstalled(MESH_OPT_PACKAGE_NAME))
{
return;
}

if (EditorUtility.DisplayDialog(
DIALOG_TITLE,
string.Format(DIALOG_MESSAGE, "Mesh opt compression", MESH_OPT_PACKAGE_NAME),
DIALOG_OK,
DIALOG_CANCEL))
{
PackageManagerHelper.AddPackage(MESH_OPT_PACKAGE_NAME);
}
else
{
avatarConfigTarget.UseMeshOptCompression = false;
useMeshOptCompression.SetValueWithoutNotify(false);
}

serializedObject.ApplyModifiedProperties();
}
);
}

private void SetupTextureChannel()
{
var items = new List<string>();
foreach (TextureChannel textureChannel in Enum.GetValues(typeof(TextureChannel)))
{
items.Add(textureChannel.ToString());
}

VisualElement MakeItem()
{
var toggle = new Toggle();
toggle.style.alignItems = Align.Center;
return toggle;
}

void BindItem(VisualElement e, int i)
{
var toggle = (Toggle) e;
toggle.label = items[i];
if (avatarConfigTarget.TextureChannel.Contains((TextureChannel) i))
{
toggle.SetValueWithoutNotify(true);
}

toggle.RegisterValueChangedCallback(x =>
{
if (x.newValue)
{
var textureChannels = new List<TextureChannel>();
textureChannels.Add((TextureChannel) i);
avatarConfigTarget.TextureChannel = textureChannels.ToArray();
}
else
{
var textureChannels = avatarConfigTarget.TextureChannel.ToList();
textureChannels.Remove((TextureChannel) i);
avatarConfigTarget.TextureChannel = textureChannels.ToArray();
}
serializedObject.ApplyModifiedProperties();
});
}

var listView = root.Q<ListView>();
listView.style.height = 30 * Enum.GetValues(typeof(TextureChannel)).Length + 2;
listView.makeItem = MakeItem;
listView.bindItem = BindItem;
listView.itemsSource = items;
listView.selectionType = SelectionType.Multiple;

listView.onItemsChosen += Debug.Log;
listView.onSelectionChange += Debug.Log;
}

private void SetupMorphTargets()
{
morphTargetLabels = AvatarMorphTarget.MorphTargetAvatarAPI.Select(x => new Label(x)).ToList();
morphTargetsParentVisualElement = new Dictionary<VisualElement, string>();
selectedMorphTargets = root.Q<VisualElement>("SelectedMorphTargets");

for (var i = 0; i < avatarConfigTarget.MorphTargets.Count; i++)
{
var defaultIndex = AvatarMorphTarget.MorphTargetAvatarAPI.IndexOf(avatarConfigTarget.MorphTargets[i]);
CreateNewElement(defaultIndex);
}

var addButton = root.Q<Button>("AddButton");
addButton.clicked += OnAddButtonClicked;
}

private void OnAddButtonClicked()
{
Undo.RecordObject(avatarConfigTarget, ADD_MORPH_TARGET);
avatarConfigTarget.MorphTargets.Add(AvatarMorphTarget.MorphTargetAvatarAPI[0]);
EditorUtility.SetDirty(avatarConfigTarget);
CreateNewElement(0);
}

private void CreateNewElement(int popFieldDefaultIndex)
{
var parent = new VisualElement();
parent.style.flexDirection = new StyleEnum<FlexDirection>(FlexDirection.Row);
parent.style.justifyContent = new StyleEnum<Justify>(Justify.SpaceBetween);

morphTargetsParentVisualElement.Add(parent, AvatarMorphTarget.MorphTargetAvatarAPI[popFieldDefaultIndex]);
parent.Add(CreatePopupField(popFieldDefaultIndex, parent));
parent.Add(CreateRemoveButton(parent));
selectedMorphTargets.Add(parent);
}

private PopupField<Label> CreatePopupField(int defaultIndex, VisualElement parent)
{
return new PopupField<Label>(string.Empty,
morphTargetLabels,
defaultIndex,
x =>
{
avatarConfigTarget.MorphTargets[GetIndex(morphTargetsParentVisualElement[parent])] = x.text;
morphTargetsParentVisualElement[parent] = x.text;
return x.text;
},
x => x.text);
}

private VisualElement CreateRemoveButton(VisualElement parent)
{
var removeButton = new Button(() =>
{
Undo.RecordObject(avatarConfigTarget, DELETE_MORPH_TARGET);
avatarConfigTarget.MorphTargets.RemoveAt(GetIndex(morphTargetsParentVisualElement[parent]));
selectedMorphTargets.Remove(parent);
EditorUtility.SetDirty(avatarConfigTarget);
});
removeButton.text = REMOVE_BUTTON_TEXT;
return removeButton;
}

private int GetIndex(string morphTarget)
{
return avatarConfigTarget.MorphTargets.FindIndex(x => x == morphTarget);
}
}
}
16 changes: 13 additions & 3 deletions Editor/UI/AvatarConfigEditor/AvatarConfigEditor.uxml
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<Style src="../EditorWindows/CommonStyle.uss" />
<ui:IMGUIContainer name="DefaultInspector" />
<ui:Foldout text="Morph Targets" style="margin-left: 13px; margin-right: 0; margin-top: 5px;">
<uie:EnumField label="Lod" value="High" type="ReadyPlayerMe.Core.Lod, ReadyPlayerMe.Core" name="Lod" />
<uie:EnumField label="Pose" value="APose" type="ReadyPlayerMe.Core.Pose, ReadyPlayerMe.Core" name="Pose" />
<ui:Toggle label="Use Hands" name="UseHands" value="false" style="margin-left: 3px; height: 16px; margin-top: 3px;" />
<uie:EnumField label="TextureAtlas" value="High" type="ReadyPlayerMe.Core.TextureAtlas, ReadyPlayerMe.Core" name="TextureAtlas" />
<ui:SliderInt picking-mode="Ignore" label="TextureSizeLimit" value="0" high-value="1024" low-value="256" show-input-field="true" name="TextureSizeLimit" />
<ui:Foldout text="Texture Channel" name="TextureChannel" value="false" style="flex-direction: column; margin-left: 4px; flex-grow: 0; margin-right: 4px; margin-top: 4px; align-items: stretch; justify-content: space-between; flex-shrink: 0;">
<ui:ListView focusable="true" reorderable="true" show-border="true" show-alternating-row-backgrounds="All" style="height: auto; flex-shrink: 0; flex-grow: 0; justify-content: flex-start; border-left-width: 1px; border-right-width: 1px; border-top-width: 1px; border-bottom-width: 1px;" />
</ui:Foldout>
<ui:Foldout text="Optimization Packages" name="OptimizationPackages" value="false" style="flex-shrink: 0; margin-top: 4px; margin-left: 4px; margin-right: 4px;">
<ui:Toggle label="Use Draco Compression" name="UseDracoCompression" style="flex-direction: row; align-items: center; justify-content: flex-start; margin-left: 3px; margin-top: 3px;" />
<ui:Toggle label="Use Mesh Opt Compression" name="UseMeshOptCompression" style="margin-left: 3px; margin-top: 3px; align-items: center; flex-direction: row; flex-grow: 0; flex-shrink: 0; flex-wrap: nowrap; position: relative; overflow: hidden; opacity: 1;" />
</ui:Foldout>
<ui:Foldout text="Morph Targets" name="MorphTargets" value="false" style="margin-left: 4px; margin-right: 4px; margin-top: 4px; margin-bottom: 1px;">
<ui:VisualElement name="SelectedMorphTargets" style="flex-grow: 0; margin-top: 5px; -unity-text-outline-width: 0;" />
<ui:Button text="Add" display-tooltip-when-elided="true" name="AddButton" style="margin-top: 5px;" />
</ui:Foldout>
Expand Down
Loading

0 comments on commit ee4bd9e

Please sign in to comment.