Skip to content

Commit

Permalink
feat: implement wearable preview (#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
poi-vrc authored Aug 29, 2023
1 parent 93fc67d commit bbe562c
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 6 deletions.
142 changes: 142 additions & 0 deletions Packages/com.chocopoi.vrc.dressingtools/Editor/DTEditorUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Chocopoi.AvatarLib.Animations;
using Chocopoi.DressingTools.Lib;
using Chocopoi.DressingTools.Lib.Cabinet;
using Chocopoi.DressingTools.Lib.Cabinet.Modules;
using Chocopoi.DressingTools.Lib.Extensibility.Providers;
using Chocopoi.DressingTools.Lib.Logging;
using Chocopoi.DressingTools.Lib.Proxy;
using Chocopoi.DressingTools.Lib.Wearable;
using Chocopoi.DressingTools.Lib.Wearable.Modules;
Expand All @@ -35,6 +38,8 @@ internal class DTEditorUtils
{
private const string BoneNameMappingsPath = "Packages/com.chocopoi.vrc.dressingtools/Resources/boneNameMappings.json";

private const string PreviewAvatarNamePrefix = "DTPreview_";

private static Dictionary<string, System.Type> s_reflectionTypeCache = new Dictionary<string, System.Type>();

private static readonly System.Random Random = new System.Random();
Expand Down Expand Up @@ -649,5 +654,142 @@ public static string RandomString(int length)
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.Next(s.Length)]).ToArray());
}

public static bool PreviewActive { get; private set; }

public static void CleanUpPreviewAvatars()
{
PreviewActive = false;
// remove all existing preview objects;
GameObject[] allObjects = Object.FindObjectsOfType<GameObject>();
foreach (var obj in allObjects)
{
if (obj != null && obj.name.StartsWith(PreviewAvatarNamePrefix))
{
Object.DestroyImmediate(obj);
}
}
}

public static void UpdatePreviewAvatar(GameObject targetAvatar, WearableConfig newWearableConfig, GameObject newWearable)
{
PreviewAvatar(targetAvatar, newWearable, out var previewAvatar, out var previewWearable);

if (previewAvatar == null || previewWearable == null)
{
return;
}

var cabinet = GetAvatarCabinet(previewAvatar);

if (cabinet == null)
{
return;
}

if (!CabinetConfig.TryDeserialize(cabinet.configJson, out var cabinetConfig))
{
Debug.LogError("[DressingTools] Unable to deserialize cabinet config for preview");
return;
}

var report = new DTReport();
var cabCtx = new ApplyCabinetContext()
{
report = report,
cabinetConfig = cabinetConfig,
avatarGameObject = previewAvatar,
avatarDynamics = ScanDynamics(previewAvatar, true),
wearableContexts = new Dictionary<DTCabinetWearable, ApplyWearableContext>()
};

var providers = WearableModuleProviderLocator.Instance.GetAllProviders();
foreach (var provider in providers)
{
var wearCtx = new ApplyWearableContext()
{
wearableConfig = newWearableConfig,
wearableGameObject = previewWearable,
wearableDynamics = ScanDynamics(previewWearable)
};

var module = FindWearableModule(newWearableConfig, provider.ModuleIdentifier);
if (!provider.OnPreviewWearable(cabCtx, wearCtx, module))
{
Debug.LogError("[DressingTools] Error applying wearable in preview!");
return;
}
}
}

public static void PreviewAvatar(GameObject targetAvatar, GameObject targetWearable, out GameObject previewAvatar, out GameObject previewWearable)
{
if (targetAvatar == null || targetWearable == null)
{
CleanUpPreviewAvatars();
PreviewActive = false;
previewAvatar = null;
previewWearable = null;
return;
}

var objName = PreviewAvatarNamePrefix + targetAvatar.name;
previewAvatar = GameObject.Find(objName);

// find path of wearable
var path = IsGrandParent(targetAvatar.transform, targetWearable.transform) ?
AnimationUtils.GetRelativePath(targetWearable.transform, targetAvatar.transform) :
targetWearable.name;

// return existing preview object if any
if (previewAvatar != null)
{
var wearableTransform = previewAvatar.transform.Find(path);

// valid preview
if (wearableTransform != null)
{
PreviewActive = true;
previewWearable = wearableTransform.gameObject;
return;
}

// recreate the preview
}

// clean up and recreate
CleanUpPreviewAvatars();

// create a copy of the avatar and wearable
previewAvatar = Object.Instantiate(targetAvatar);
previewAvatar.name = objName;

var newPos = previewAvatar.transform.position;
newPos.x -= 20;
previewAvatar.transform.position = newPos;

// if wearable is not inside avatar, we instantiate a new copy
if (!IsGrandParent(targetAvatar.transform, targetWearable.transform))
{
previewWearable = Object.Instantiate(targetWearable);
previewWearable.transform.position = newPos;
previewWearable.transform.SetParent(previewAvatar.transform);
}
else
{
previewWearable = previewAvatar.transform.Find(path).gameObject;
}

// select in sceneview
FocusGameObjectInSceneView(previewAvatar);

PreviewActive = true;
}

public static void FocusGameObjectInSceneView(GameObject go)
{
Selection.activeGameObject = go;
SceneView.FrameLastActiveSceneView();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ private void OnAddToCabinetButtonClick()

DTEditorUtils.AddCabinetWearable(cabinetConfig, _view.TargetAvatar, _view.Config, _view.TargetWearable);

// remove previews
DTEditorUtils.CleanUpPreviewAvatars();
DTEditorUtils.FocusGameObjectInSceneView(_view.TargetAvatar);

// reset and return
_view.ResetWizardAndConfigView();
_view.SelectTab(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
* You should have received a copy of the GNU General Public License along with DressingTools. If not, see <https://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using Chocopoi.AvatarLib.Animations;
using Chocopoi.DressingTools.Cabinet.Modules;
Expand All @@ -25,6 +24,7 @@
using Chocopoi.DressingTools.Lib.Wearable;
using Chocopoi.DressingTools.UIBase.Views;
using Chocopoi.DressingTools.Wearable.Modules;
using UnityEditor;
using UnityEngine;

namespace Chocopoi.DressingTools.UI.Presenters.Modules
Expand Down Expand Up @@ -264,6 +264,7 @@ private void UpdateToggles(Transform root, List<AnimationToggle> toggles, List<T
toggleData.isInvalid = false;
toggle.path = DTEditorUtils.GetRelativePath(toggleData.gameObject.transform, root);
toggle.state = toggleData.state;
_parentView.UpdateAvatarPreview();
}
else
{
Expand All @@ -274,6 +275,7 @@ private void UpdateToggles(Transform root, List<AnimationToggle> toggles, List<T
{
toggles.Remove(toggle);
toggleDataList.Remove(toggleData);
_parentView.UpdateAvatarPreview();
};

toggleDataList.Add(toggleData);
Expand Down Expand Up @@ -340,6 +342,7 @@ private void UpdateBlendshapes(Transform root, List<AnimationBlendshapeValue> bl
blendshape.blendshapeName = blendshapeData.availableBlendshapeNames[blendshapeData.selectedBlendshapeIndex];
blendshape.value = blendshapeData.value;
_parentView.UpdateAvatarPreview();
}
else
{
Expand All @@ -352,6 +355,7 @@ private void UpdateBlendshapes(Transform root, List<AnimationBlendshapeValue> bl
{
blendshapes.Remove(blendshape);
blendshapeDataList.Remove(blendshapeData);
_parentView.UpdateAvatarPreview();
};

blendshapeDataList.Add(blendshapeData);
Expand Down Expand Up @@ -390,11 +394,12 @@ private bool IsGameObjectUsedInToggles(GameObject go, List<ToggleData> toggleDat

private void UpdateAvatarOnWearToggleSuggestions(PresetViewData presetData)
{
presetData.toggleSuggestions.Clear();

var targetAvatar = _parentView.TargetAvatar;
var targetWearable = _parentView.TargetWearable;

presetData.toggleSuggestions.Clear();
if (_cabinetConfig != null)
if (targetAvatar != null && targetWearable != null && _cabinetConfig != null)
{
var armatureName = _cabinetConfig.avatarArmatureName;
var avatarTrans = targetAvatar.transform;
Expand All @@ -417,6 +422,7 @@ private void UpdateAvatarOnWearToggleSuggestions(PresetViewData presetData)
state = !childTrans.gameObject.activeSelf
});
UpdateAnimationGenerationAvatarOnWear();
_parentView.UpdateAvatarPreview();
}
};
presetData.toggleSuggestions.Add(toggleSuggestion);
Expand All @@ -427,9 +433,15 @@ private void UpdateAvatarOnWearToggleSuggestions(PresetViewData presetData)

private void UpdateWearableOnWearToggleSuggestions(PresetViewData presetData)
{
presetData.toggleSuggestions.Clear();

var targetWearable = _parentView.TargetWearable;

presetData.toggleSuggestions.Clear();
if (targetWearable != null)
{
return;
}

var wearableTrans = targetWearable.transform;

// TODO: we can't obtain wearable armature name here, listing everything at the root for now
Expand All @@ -438,7 +450,7 @@ private void UpdateWearableOnWearToggleSuggestions(PresetViewData presetData)
for (var i = 0; i < wearableTrans.childCount; i++)
{
var childTrans = wearableTrans.GetChild(i);
if (childTrans != targetWearable.transform && !IsGameObjectUsedInToggles(childTrans.gameObject, presetData.toggles))
if (!IsGameObjectUsedInToggles(childTrans.gameObject, presetData.toggles))
{
var toggleSuggestion = new ToggleSuggestionData
{
Expand All @@ -452,6 +464,7 @@ private void UpdateWearableOnWearToggleSuggestions(PresetViewData presetData)
state = !childTrans.gameObject.activeSelf
});
UpdateAnimationGenerationWearableOnWear();
_parentView.UpdateAvatarPreview();
}
};
presetData.toggleSuggestions.Add(toggleSuggestion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ private void SubscribeEvents()
_view.TargetAvatarOrWearableChange += OnTargetAvatarOrWearableChange;
_view.PreviousButtonClick += OnPreviousButtonClick;
_view.NextButtonClick += OnNextButtonClick;
_view.PreviewButtonClick += OnPreviewButtonClick;
}

private void UnsubscribeEvents()
Expand All @@ -56,6 +57,7 @@ private void UnsubscribeEvents()
_view.TargetAvatarOrWearableChange -= OnTargetAvatarOrWearableChange;
_view.PreviousButtonClick -= OnPreviousButtonClick;
_view.NextButtonClick -= OnNextButtonClick;
_view.PreviewButtonClick -= OnPreviewButtonClick;
}

private void OnForceUpdateView()
Expand All @@ -69,6 +71,25 @@ private void OnTargetAvatarOrWearableChange()
AutoSetup();
}

private void OnPreviewButtonClick()
{
if (_view.PreviewActive)
{
DTEditorUtils.CleanUpPreviewAvatars();
DTEditorUtils.FocusGameObjectInSceneView(_view.TargetAvatar);
}
else
{
UpdateAvatarPreview();
}
}

public void UpdateAvatarPreview()
{
GenerateConfig();
DTEditorUtils.UpdatePreviewAvatar(_view.TargetAvatar, _view.Config, _view.TargetWearable);
}

private void AutoSetupMapping()
{
// cabinet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,5 +218,10 @@ public override void OnGUI()
}

public bool IsValid() => _presenter.IsValid();

public void UpdateAvatarPreview()
{
throw new NotImplementedException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ namespace Chocopoi.DressingTools.UI.Views
[ExcludeFromCodeCoverage]
internal class WearableSetupWizardView : EditorViewBase, IWearableSetupWizardView
{
private static readonly Color PreviewButtonActiveColour = new Color(0.5f, 1, 0.5f, 1);

public event Action TargetAvatarOrWearableChange { add { _dressingSubView.TargetAvatarOrWearableChange += value; } remove { _dressingSubView.TargetAvatarOrWearableChange -= value; } }
public event Action PreviousButtonClick;
public event Action NextButtonClick;
public event Action PreviewButtonClick;

public ArmatureMappingWearableModuleConfig ArmatureMappingModuleConfig { get; set; }
public MoveRootWearableModuleConfig MoveRootModuleConfig { get; set; }
Expand All @@ -55,7 +58,7 @@ internal class WearableSetupWizardView : EditorViewBase, IWearableSetupWizardVie
public bool ShowArmatureNotFoundHelpBox { get; set; }
public bool ShowArmatureGuessedHelpBox { get; set; }
public bool ShowCabinetConfigErrorHelpBox { get; set; }

public bool PreviewActive => DTEditorUtils.PreviewActive;

private WearableSetupWizardPresenter _presenter;
private IDressingSubView _dressingSubView;
Expand Down Expand Up @@ -181,6 +184,13 @@ private void DrawAnimateStep()
EndDisabled();
}

private void PreviewButton()
{
if (PreviewActive) GUI.backgroundColor = PreviewButtonActiveColour;
Button("Preview", PreviewButtonClick, GUILayout.ExpandWidth(false));
GUI.backgroundColor = Color.white;
}

public override void OnGUI()
{
Toolbar(ref _currentStep, new string[] { " 1.\nMapping", "2.\nAnimate", "3.\nIntegrate", "4.\nOptimize" });
Expand All @@ -195,6 +205,7 @@ public override void OnGUI()
}
EndDisabled();
GUILayout.FlexibleSpace();
PreviewButton();
Button(CurrentStep == 3 ? "Finish!" : "Next >", NextButtonClick);
}
EndHorizontal();
Expand Down Expand Up @@ -234,5 +245,7 @@ public override void OnGUI()
HelpBox("Optimization wizard not implemented", MessageType.Info);
}
}

public void UpdateAvatarPreview() => _presenter.UpdateAvatarPreview();
}
}
Loading

0 comments on commit bbe562c

Please sign in to comment.