Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: ApplySpecialMapping in ComponentInfo #673

Merged
merged 7 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 77 additions & 1 deletion API-Editor/ComponentInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Anatawa12.AvatarOptimizer.API
{
Expand Down Expand Up @@ -47,6 +48,9 @@ internal sealed override void CollectMutationsInternal(Component component,
ComponentMutationsCollector collector) =>
CollectMutations((TComponent)component, collector);

internal override void ApplySpecialMappingInternal(Component component, MappingSource collector) =>
ApplySpecialMapping((TComponent)component, collector);

/// <summary>
/// Collects runtime mutations by <see cref="component"/>.
/// You have to call <see cref="collector"/>.<see cref="ComponentMutationsCollector.ModifyProperties"/>
Expand All @@ -68,7 +72,18 @@ internal sealed override void CollectMutationsInternal(Component component,
protected virtual void CollectMutations(TComponent component, ComponentMutationsCollector collector)
{
}


/// <summary>
/// Applies some property mapping to your component.
/// For object replacements, AAO processes automatically so you don't have to implement that in this method.
/// </summary>
/// <param name="component">The component.</param>
/// <param name="mappingSource">The mapping source</param>
[PublicAPI]
protected virtual void ApplySpecialMapping(TComponent component, MappingSource mappingSource)
{
}

// Important note for future AAO developer
// 1. You MUST NOT add abstract method onto this class
// 2. When you added some virtual methods onto this class, implementer MAY NOT implement that method
Expand Down Expand Up @@ -194,5 +209,66 @@ internal ComponentMutationsCollector()

[PublicAPI]
public abstract void ModifyProperties([NotNull] Component component, [NotNull] IEnumerable<string> properties);

[PublicAPI]
public void ModifyProperties([NotNull] Component component, [NotNull] string[] properties) =>
ModifyProperties(component, (IEnumerable<string>) properties);
}

public abstract class MappingSource
{
internal MappingSource()
{
}

[PublicAPI]
public abstract MappedComponentInfo<T> GetMappedComponent<T>(T component) where T : Component;

[PublicAPI]
public abstract MappedComponentInfo<GameObject> GetMappedGameObject(GameObject component);
}

public abstract class MappedComponentInfo<T> where T : Object
{
internal MappedComponentInfo()
{
}

/// <summary>
/// The mapped component (or GameObject).
/// The component may be removed without mapped component.
/// If there are not mapped component, this will be null.
///
/// Even if the component is removed without mapped component,
/// each animation property can be mapped to another component.
/// </summary>
[PublicAPI]
public abstract T MappedComponent { get; }

/// <summary>
/// Maps animation property name to component and MappedPropertyInfo.
/// If the property is not removed, returns true and <paramref name="found"/> is set.
/// If the property is removed, returns false and <paramref name="found"/> will be default.
/// </summary>
/// <param name="property">The name of property will be mapped</param>
/// <param name="found">The result parameter</param>
/// <returns>Whether if the property is successfully mapped or removed</returns>
[PublicAPI]
public abstract bool TryMapProperty(string property, out MappedPropertyInfo found);
}

public readonly struct MappedPropertyInfo
{
[PublicAPI]
public Object Component { get; }

[PublicAPI]
public string Property { get; }

internal MappedPropertyInfo(Object component, string property)
{
Component = component;
Property = property;
}
}
}
1 change: 1 addition & 0 deletions API-Editor/Internal/InternalParts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ internal ComponentInformation()

internal abstract void CollectDependencyInternal(Component component, ComponentDependencyCollector collector);
internal abstract void CollectMutationsInternal(Component component, ComponentMutationsCollector collector);
internal abstract void ApplySpecialMappingInternal(Component component, MappingSource mappingSource);
}
}
173 changes: 173 additions & 0 deletions Editor/APIInternal/ComponentInfos.VRCSDK.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#if AAO_VRCSDK3_AVATARS
using System;
using System.Collections.Generic;
using System.Linq;
using Anatawa12.AvatarOptimizer.API;
using Anatawa12.AvatarOptimizer.ErrorReporting;
using JetBrains.Annotations;
using UnityEngine;
using VRC.SDK3;
using VRC.Core;
Expand Down Expand Up @@ -38,6 +41,93 @@ protected override void CollectDependency(T component,
collector.MarkEntrypoint();
collector.AddDependency(component.GetComponent<PipelineManager>()).EvenIfDependantDisabled();
}

protected override void CollectMutations(T component, ComponentMutationsCollector collector)
{
base.CollectMutations(component, collector);
switch (component.lipSync)
{
case VRC_AvatarDescriptor.LipSyncStyle.Default:
// TODO
break;
case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBone:
collector.TransformRotation(component.lipSyncJawBone);
break;
case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when component.VisemeSkinnedMesh != null:
{
collector.ModifyProperties(component.VisemeSkinnedMesh,
new[] { $"blendShape.{component.MouthOpenBlendShapeName}" });
break;
}
case VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape when component.VisemeSkinnedMesh != null:
{
collector.ModifyProperties(component.VisemeSkinnedMesh,
component.VisemeBlendShapes.Select(blendShape => $"blendShape.{blendShape}"));
break;
}
case VRC_AvatarDescriptor.LipSyncStyle.VisemeParameterOnly:
// NOP
break;
default:
throw new ArgumentOutOfRangeException();
}
}

protected override void ApplySpecialMapping(T component, MappingSource mappingSource)
{
base.ApplySpecialMapping(component, mappingSource);

switch (component.lipSync)
{
case VRC_AvatarDescriptor.LipSyncStyle.Default:
// TODO
break;
case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBone:
break;
case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when component.VisemeSkinnedMesh != null:
{
var info = mappingSource.GetMappedComponent(component.VisemeSkinnedMesh);
if (info.TryMapProperty($"blendShape.{component.MouthOpenBlendShapeName}", out var mapped))
{
component.VisemeSkinnedMesh = mapped.Component as SkinnedMeshRenderer;
component.MouthOpenBlendShapeName = ParseBlendShapeProperty(mapped.Property);
}
else
{
component.VisemeSkinnedMesh = null;
}
break;
}
case VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape when component.VisemeSkinnedMesh != null:
{
var info = mappingSource.GetMappedComponent(component.VisemeSkinnedMesh);
component.VisemeSkinnedMesh = info.MappedComponent;
var removed = false;
foreach (ref var shapeName in component.VisemeBlendShapes.AsSpan())
{
if (info.TryMapProperty($"blendShape.{shapeName}", out var mapped)
&& mapped.Component == info.MappedComponent)
shapeName = ParseBlendShapeProperty(mapped.Property);
else
removed = true;
}
if (removed)
BuildReport.LogFatal("ApplyObjectMapping:VRCAvatarDescriptor:viseme BlendShape Removed");
break;
}
case VRC_AvatarDescriptor.LipSyncStyle.VisemeParameterOnly:
// NOP
break;
default:
throw new ArgumentOutOfRangeException();
}
}

[NotNull]
private static string ParseBlendShapeProperty(string prop) =>
prop.StartsWith("blendShape.", StringComparison.Ordinal)
? prop.Substring("blendShape.".Length)
: throw new Exception("invalid blendShape property");
}

[ComponentInformation(typeof(VRCAvatarDescriptor))]
Expand Down Expand Up @@ -77,6 +167,89 @@ void AddCollider(VRCAvatarDescriptor.ColliderConfig collider)
}
}
}

protected override void CollectMutations(VRCAvatarDescriptor component, ComponentMutationsCollector collector)
{
base.CollectMutations(component, collector);

if (component.enableEyeLook)
{
var leftEye = component.customEyeLookSettings.leftEye;
var rightEye = component.customEyeLookSettings.rightEye;

if (leftEye) collector.TransformRotation(leftEye);
if (rightEye) collector.TransformRotation(rightEye);

switch (component.customEyeLookSettings.eyelidType)
{
case VRCAvatarDescriptor.EyelidType.None:
break;
case VRCAvatarDescriptor.EyelidType.Bones:
{
foreach (var eyelids in new[]
{
component.customEyeLookSettings.lowerLeftEyelid,
component.customEyeLookSettings.upperLeftEyelid,
component.customEyeLookSettings.lowerRightEyelid,
component.customEyeLookSettings.upperRightEyelid,
})
collector.TransformRotation(eyelids);
}
break;
case VRCAvatarDescriptor.EyelidType.Blendshapes
when component.customEyeLookSettings.eyelidsSkinnedMesh != null:
{
var skinnedMeshRenderer = component.customEyeLookSettings.eyelidsSkinnedMesh;
var mesh = skinnedMeshRenderer.sharedMesh;

collector.ModifyProperties(skinnedMeshRenderer,
from index in component.customEyeLookSettings.eyelidsBlendshapes
where 0 <= index && index < mesh.blendShapeCount
select $"blendShape.{mesh.GetBlendShapeName(index)}");
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}

protected override void ApplySpecialMapping(VRCAvatarDescriptor component, MappingSource mappingSource)
{
base.ApplySpecialMapping(component, mappingSource);

if (component.enableEyeLook)
{
switch (component.customEyeLookSettings.eyelidType)
{
case VRCAvatarDescriptor.EyelidType.None:
break;
case VRCAvatarDescriptor.EyelidType.Bones:
break;
case VRCAvatarDescriptor.EyelidType.Blendshapes
when component.customEyeLookSettings.eyelidsSkinnedMesh != null:
{
var info = mappingSource.GetMappedComponent(component.customEyeLookSettings.eyelidsSkinnedMesh);
component.customEyeLookSettings.eyelidsSkinnedMesh = info.MappedComponent;
var removed = false;
foreach (ref var eyelidsBlendshape in component.customEyeLookSettings.eyelidsBlendshapes.AsSpan())
{
if (info.TryMapProperty(VProp.BlendShapeIndex(eyelidsBlendshape), out var mapped)
&& mapped.Component == info.MappedComponent)
eyelidsBlendshape = VProp.ParseBlendShapeIndex(mapped.Property);
else
removed = true;
}

if (removed)
BuildReport.LogFatal("ApplyObjectMapping:VRCAvatarDescriptor:eyelids BlendShape Removed");
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

[ComponentInformation(typeof(VRC.SDKBase.VRCStation))]
Expand Down
78 changes: 0 additions & 78 deletions Editor/AnimatorParsers/AnimatorParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,84 +242,6 @@ void MergeLayer(VRCAvatarDescriptor.AnimLayerType type, bool alwaysApplied, floa

// TPose and IKPose should only affect to Humanoid so skip here~

switch (descriptor.lipSync)
{
// AvatarDescriptorから収集
case VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape when descriptor.VisemeSkinnedMesh != null:
{
var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh;
var updater = modificationsContainer.ModifyObject(skinnedMeshRenderer);
foreach (var blendShape in descriptor.VisemeBlendShapes)
updater.AddModificationAsNewLayer($"blendShape.{blendShape}", AnimationFloatProperty.Variable(descriptor));
break;
}
case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when descriptor.VisemeSkinnedMesh != null:
{
var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh;
var shape = descriptor.MouthOpenBlendShapeName;

modificationsContainer.ModifyObject(skinnedMeshRenderer)
.AddModificationAsNewLayer($"blendShape.{shape}", AnimationFloatProperty.Variable(descriptor));
break;
}
}

if (descriptor.enableEyeLook)
{
var leftEye = descriptor.customEyeLookSettings.leftEye;
var rightEye = descriptor.customEyeLookSettings.rightEye;
if (leftEye)
{
var updater = modificationsContainer.ModifyObject(leftEye);
foreach (var prop in TransformRotationAnimationKeys)
updater.AddModificationAsNewLayer(prop, AnimationFloatProperty.Variable(descriptor));
}


if (rightEye)
{
var updater = modificationsContainer.ModifyObject(rightEye);
foreach (var prop in TransformRotationAnimationKeys)
updater.AddModificationAsNewLayer(prop, AnimationFloatProperty.Variable(descriptor));
}

switch (descriptor.customEyeLookSettings.eyelidType)
{
case VRCAvatarDescriptor.EyelidType.None:
break;
case VRCAvatarDescriptor.EyelidType.Bones:
{
foreach (var eyelids in new[]
{
descriptor.customEyeLookSettings.lowerLeftEyelid,
descriptor.customEyeLookSettings.upperLeftEyelid,
descriptor.customEyeLookSettings.lowerRightEyelid,
descriptor.customEyeLookSettings.upperRightEyelid,
})
{
var updater = modificationsContainer.ModifyObject(eyelids);
foreach (var prop in TransformRotationAnimationKeys)
updater.AddModificationAsNewLayer(prop, AnimationFloatProperty.Variable(descriptor));
}
}
break;
case VRCAvatarDescriptor.EyelidType.Blendshapes
when descriptor.customEyeLookSettings.eyelidsSkinnedMesh != null:
{
var skinnedMeshRenderer = descriptor.customEyeLookSettings.eyelidsSkinnedMesh;
var mesh = skinnedMeshRenderer.sharedMesh;

var updater = modificationsContainer.ModifyObject(skinnedMeshRenderer);

foreach (var blendShape in from index in descriptor.customEyeLookSettings.eyelidsBlendshapes
where 0 <= index && index < mesh.blendShapeCount
select mesh.GetBlendShapeName(index))
updater.AddModificationAsNewLayer($"blendShape.{blendShape}", AnimationFloatProperty.Variable(descriptor));
}
break;
}
}

var bodySkinnedMesh = descriptor.transform.Find("Body")?.GetComponent<SkinnedMeshRenderer>();

if (mmdWorldCompatibility && bodySkinnedMesh)
Expand Down
Loading