Skip to content

Commit

Permalink
feat: invert toggle states, disable dynamics and follow blendshape sy…
Browse files Browse the repository at this point in the history
…nc on adding to cabinet (#147)
  • Loading branch information
poi-vrc authored Aug 23, 2023
1 parent 63522b1 commit 50c76ba
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 19 deletions.
36 changes: 30 additions & 6 deletions Packages/com.chocopoi.vrc.dressingtools/Editor/DTEditorUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Chocopoi.DressingTools.Cabinet;
using Chocopoi.DressingTools.Lib.Cabinet;
using Chocopoi.DressingTools.Lib.Wearable;
using Chocopoi.DressingTools.Lib.Wearable.Modules.Providers;
using Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
Expand Down Expand Up @@ -80,8 +81,14 @@ public static DTCabinet GetAvatarCabinet(GameObject avatar, bool createIfNotExis
return comp;
}

public static void AddCabinetWearable(DTCabinet cabinet, WearableConfig config, GameObject wearableGameObject)
public static bool AddCabinetWearable(DTCabinet cabinet, WearableConfig config, GameObject wearableGameObject)
{
// do not add if there's an existing component
if (wearableGameObject.GetComponent<DTCabinetWearable>() != null)
{
return false;
}

if (PrefabUtility.IsPartOfAnyPrefab(wearableGameObject) && PrefabUtility.GetPrefabInstanceStatus(wearableGameObject) == PrefabInstanceStatus.NotAPrefab)
{
// if not in scene, we instantiate it with a prefab connection
Expand All @@ -91,14 +98,31 @@ public static void AddCabinetWearable(DTCabinet cabinet, WearableConfig config,
// parent to avatar
wearableGameObject.transform.SetParent(cabinet.transform);

if (wearableGameObject.GetComponent<DTCabinetWearable>() == null)
// add cabinet wearable component
var cabinetWearable = wearableGameObject.AddComponent<DTCabinetWearable>();

cabinetWearable.wearableGameObject = wearableGameObject;
cabinetWearable.configJson = config.Serialize().ToString(Formatting.None);

// do provider hooks
var providers = ModuleProviderLocator.Instance.GetAllProviders();
foreach (var provider in providers)
{
// add cabinet wearable component
var cabinetWearable = wearableGameObject.AddComponent<DTCabinetWearable>();
var module = DTRuntimeUtils.FindWearableModule(config, provider.ModuleIdentifier);
if (module == null)
{
// config does not have such module
continue;
}

cabinetWearable.wearableGameObject = wearableGameObject;
cabinetWearable.configJson = config.Serialize().ToString(Formatting.None);
if (!provider.OnAddWearableToCabinet(cabinet, config, wearableGameObject, module))
{
Debug.LogWarning("[DressingTools] [AddCabinetWearable] Error processing provider OnAddWearableToCabinet hook: " + provider.ModuleIdentifier);
return false;
}
}

return true;
}

public static void RemoveCabinetWearable(DTCabinet cabinet, DTCabinetWearable wearable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,6 @@ public GenerateAnimationsHook(DTReport report, DTCabinet cabinet)
_cabinet = cabinet;
}

private AnimationGenerationModuleConfig FindAnimationGenerationModuleConfig(WearableConfig config)
{
foreach (var module in config.Modules)
{
if (module.config is AnimationGenerationModuleConfig agm)
{
return agm;
}
}
return null;
}

public bool OnPreprocessAvatar()
{
EditorUtility.DisplayProgressBar("DressingTools", "Generating animations...", 0);
Expand Down Expand Up @@ -151,7 +139,7 @@ public bool OnPreprocessAvatar()
var wearableDynamics = DTRuntimeUtils.ScanDynamics(wearables[i].wearableGameObject, false);

// find the animation generation module
var module = FindAnimationGenerationModuleConfig(config);
var module = DTRuntimeUtils.FindWearableModuleConfig<AnimationGenerationModuleConfig>(config);
if (module == null)
{
Debug.Log("[DressingTools] [BuildDTCabinetCallback] [GenerateAnimationHook] " + config.Info.name + " has no AnimationGenerationModule, skipping this wearable generation");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
* You should have received a copy of the GNU General Public License along with DressingTools. If not, see <https://www.gnu.org/licenses/>.
*/

using Chocopoi.DressingTools.Lib.Cabinet;
using Chocopoi.DressingTools.Lib.Wearable.Modules;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;

namespace Chocopoi.DressingTools.Lib.Wearable.Modules.Providers
{
Expand All @@ -34,5 +36,6 @@ public abstract class ModuleProviderBase
public virtual bool OnBeforeApplyCabinet(ApplyCabinetContext ctx, IModuleConfig moduleConfig) => true;
public virtual bool OnApplyWearable(ApplyCabinetContext cabCtx, ApplyWearableContext wearCtx, IModuleConfig moduleConfig) => true;
public virtual bool OnAfterApplyCabinet(ApplyCabinetContext ctx, IModuleConfig moduleConfig) => true;
public virtual bool OnAddWearableToCabinet(ICabinet cabinet, WearableConfig config, GameObject wearableGameObject, WearableModule module) => true;
}
}
25 changes: 25 additions & 0 deletions Packages/com.chocopoi.vrc.dressingtools/Runtime/DTRuntimeUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Chocopoi.DressingTools.Lib.Cabinet;
using Chocopoi.DressingTools.Lib.Proxy;
using Chocopoi.DressingTools.Lib.Wearable;
using Chocopoi.DressingTools.Lib.Wearable.Modules;
using Chocopoi.DressingTools.Proxy;
using Newtonsoft.Json;
using UnityEngine;
Expand Down Expand Up @@ -310,6 +311,30 @@ public static Component CopyComponent(Component originalComponent, GameObject de
return destComp;
}

public static T FindWearableModuleConfig<T>(WearableConfig config) where T : IModuleConfig
{
foreach (var module in config.Modules)
{
if (module.config is T moduleConfig)
{
return moduleConfig;
}
}
return default;
}

public static WearableModule FindWearableModule(WearableConfig config, string moduleName)
{
foreach (var module in config.Modules)
{
if (moduleName == module.moduleName)
{
return module;
}
}
return null;
}

public static bool IsOriginatedFromAnyWearable(Transform root, Transform transform)
{
var found = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,56 @@ static AnimationGenerationModuleProvider()
public override IModuleConfig DeserializeModuleConfig(JObject jObject) => jObject.ToObject<AnimationGenerationModuleConfig>();

public override IModuleConfig NewModuleConfig() => new AnimationGenerationModuleConfig();

public override bool OnAddWearableToCabinet(ICabinet cabinet, WearableConfig config, GameObject wearableGameObject, WearableModule module)
{
var agm = (AnimationGenerationModuleConfig)module.config;
var avatarGameObject = cabinet.AvatarGameObject;

// invert avatar toggles
foreach (var toggle in agm.avatarAnimationOnWear.toggles)
{
var avatarToggleObj = avatarGameObject.transform.Find(toggle.path);
if (avatarToggleObj == null)
{
Debug.LogWarning("[DressingTools] [AnimationGenerationModule] Avatar toggle GameObject not found at path: " + toggle.path);
continue;
}
avatarToggleObj.gameObject.SetActive(!toggle.state);
}

// invert wearable toggles
foreach (var toggle in agm.wearableAnimationOnWear.toggles)
{
var wearableToggleObj = wearableGameObject.transform.Find(toggle.path);
if (wearableGameObject == null)
{
Debug.LogWarning("[DressingTools] [AnimationGenerationModule] Wearable toggle GameObject not found at path: " + toggle.path);
continue;
}
wearableToggleObj.gameObject.SetActive(!toggle.state);
}

// set wearable dynamics inactive
var wearableDynamics = DTRuntimeUtils.ScanDynamics(wearableGameObject, false);
var visitedDynamicsTransforms = new List<Transform>();
foreach (var dynamics in wearableDynamics)
{
if (visitedDynamicsTransforms.Contains(dynamics.Transform))
{
// skip duplicates since it's meaningless
continue;
}

// we toggle GameObjects instead of components
dynamics.GameObject.SetActive(false);

// mark as visited
visitedDynamicsTransforms.Add(dynamics.Transform);
}

return true;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,62 @@ static BlendshapeSyncModuleProvider()
public override IModuleConfig DeserializeModuleConfig(JObject jObject) => jObject.ToObject<BlendshapeSyncModuleConfig>();

public override IModuleConfig NewModuleConfig() => new BlendshapeSyncModuleConfig();

public override bool OnAddWearableToCabinet(ICabinet cabinet, WearableConfig config, GameObject wearableGameObject, WearableModule module)
{
var avatarGameObject = cabinet.AvatarGameObject;
var bsm = (BlendshapeSyncModuleConfig)module.config;

// follow blendshape sync
foreach (var bs in bsm.blendshapeSyncs)
{
var avatarSmrObj = avatarGameObject.transform.Find(bs.avatarPath);
if (avatarSmrObj == null)
{
Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync avatar GameObject at path not found: " + bs.avatarPath);
continue;
}

var avatarSmr = avatarSmrObj.GetComponent<SkinnedMeshRenderer>();
if (avatarSmr == null || avatarSmr.sharedMesh == null)
{
Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync avatar GameObject at path does not have SkinnedMeshRenderer or Mesh attached: " + bs.avatarPath);
continue;
}

var avatarBlendshapeIndex = avatarSmr.sharedMesh.GetBlendShapeIndex(bs.avatarBlendshapeName);
if (avatarBlendshapeIndex == -1)
{
Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync avatar GameObject does not have blendshape: " + bs.avatarBlendshapeName);
continue;
}

var wearableSmrObj = wearableGameObject.transform.Find(bs.wearablePath);
if (wearableSmrObj == null)
{
Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync wearable GameObject at path not found: " + bs.wearablePath);
continue;
}

var wearableSmr = wearableSmrObj.GetComponent<SkinnedMeshRenderer>();
if (wearableSmr == null)
{
Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync wearable GameObject at path does not have SkinnedMeshRenderer or Mesh attached: " + bs.avatarPath);
continue;
}

var wearableBlendshapeIndex = wearableSmr.sharedMesh.GetBlendShapeIndex(bs.wearableBlendshapeName);
if (wearableBlendshapeIndex == -1)
{
Debug.LogWarning("[DressingTools] [BlendshapeSyncProvider] Blendshape sync wearable GameObject does not have blendshape: " + bs.wearableBlendshapeName);
continue;
}

// copy value from avatar to wearable
wearableSmr.SetBlendShapeWeight(wearableBlendshapeIndex, avatarSmr.GetBlendShapeWeight(avatarBlendshapeIndex));
}

return true;
}
}
}

0 comments on commit 50c76ba

Please sign in to comment.