avatar/Assets/VRCFury/Scripts/Editor/VF/Feature/Base/FeatureBuilder.cs

200 lines
8.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using VF.Builder;
using VF.Model;
using VF.Model.Feature;
using VF.Model.StateAction;
using VRC.SDK3.Avatars.Components;
namespace VF.Feature.Base {
public abstract class FeatureBuilder {
public AvatarManager manager;
public ClipBuilder clipBuilder;
public string tmpDirParent;
public string tmpDir;
public GameObject avatarObject;
public GameObject originalObject;
public GameObject featureBaseObject;
public Action<FeatureModel> addOtherFeature;
public int uniqueModelNum;
public List<FeatureModel> allFeaturesInRun;
public List<FeatureBuilder> allBuildersInRun;
public GameObject editorObject;
public MutableManager mutableManager;
public virtual string GetEditorTitle() {
return null;
}
public virtual VisualElement CreateEditor(SerializedProperty prop) {
return null;
}
public virtual bool AvailableOnAvatar() {
return true;
}
public virtual bool AvailableOnProps() {
return true;
}
public virtual bool ShowInMenu() {
return true;
}
public ControllerManager GetFx() {
return manager.GetController(VRCAvatarDescriptor.AnimLayerType.FX);
}
protected VFABool CreatePhysBoneResetter(List<GameObject> resetPhysbones, string name) {
if (resetPhysbones == null || resetPhysbones.Count == 0) return null;
var fx = GetFx();
var layer = fx.NewLayer(name + "_PhysBoneReset");
var param = fx.NewTrigger(name + "_PhysBoneReset");
var idle = layer.NewState("Idle");
var pause = layer.NewState("Pause");
var reset1 = layer.NewState("Reset").Move(pause, 1, 0);
var reset2 = layer.NewState("Reset").Move(idle, 1, 0);
idle.TransitionsTo(pause).When(param.IsTrue());
pause.TransitionsTo(reset1).When(fx.Always());
reset1.TransitionsTo(reset2).When(fx.Always());
reset2.TransitionsTo(idle).When(fx.Always());
var resetClip = manager.GetClipStorage().NewClip(name + "_PhysBoneReset");
foreach (var physBone in resetPhysbones) {
if (physBone == null) {
Debug.LogWarning("Physbone object in physboneResetter is missing!: " + name);
continue;
}
clipBuilder.Enable(resetClip, physBone, false);
}
reset1.WithAnimation(resetClip);
reset2.WithAnimation(resetClip);
return param;
}
protected static bool StateExists(State state) {
return state != null;
}
protected AnimationClip LoadState(string name, State state) {
if (state.actions.Count == 0) {
return manager.GetClipStorage().GetNoopClip();
}
var firstClip = state.actions
.OfType<AnimationClipAction>()
.Select(action => action.clip)
.FirstOrDefault();
void RewriteClip(AnimationClip c) {
ClipCopier.Rewrite(c, fromObj: featureBaseObject, fromRoot: avatarObject);
}
AnimationClip clip;
if (firstClip) {
clip = mutableManager.CopyRecursive(firstClip, saveParent: manager.GetClipStorage().GetStorageRoot());
RewriteClip(clip);
} else {
clip = manager.GetClipStorage().NewClip(name);
}
foreach (var action in state.actions) {
switch (action) {
case FlipbookAction flipbook:
if (flipbook.obj != null) {
// If we animate the frame to a flat number, unity can internally do some weird tweening
// which can result in it being just UNDER our target, (say 0.999 instead of 1), resulting
// in unity displaying frame 0 instead of 1. Instead, we target framenum+0.5, so there's
// leniency around it.
var frameAnimNum = (float)(Math.Floor((double)flipbook.frame) + 0.5);
clip.SetCurve(
clipBuilder.GetPath(flipbook.obj),
typeof(SkinnedMeshRenderer),
"material._FlipbookCurrentFrame",
ClipBuilder.OneFrame(frameAnimNum));
}
break;
case AnimationClipAction actionClip:
if (actionClip.clip != firstClip) {
var copy = mutableManager.CopyRecursive(actionClip.clip, "Copy of " + actionClip.clip.name);
RewriteClip(copy);
ClipCopier.Copy(copy, clip);
AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(copy));
}
break;
case ObjectToggleAction toggle:
if (toggle.obj == null) {
Debug.LogWarning("Missing object in action: " + name);
} else {
var restingState = toggle.obj.activeSelf;
clipBuilder.Enable(clip, toggle.obj, !restingState);
}
break;
case BlendShapeAction blendShape:
var foundOne = false;
foreach (var skin in avatarObject.GetComponentsInChildren<SkinnedMeshRenderer>(true)) {
if (!skin.sharedMesh) continue;
var blendShapeIndex = skin.sharedMesh.GetBlendShapeIndex(blendShape.blendShape);
if (blendShapeIndex < 0) continue;
foundOne = true;
//var defValue = skin.GetBlendShapeWeight(blendShapeIndex);
clipBuilder.BlendShape(clip, skin, blendShape.blendShape, blendShape.blendShapeValue);
}
if (!foundOne) {
Debug.LogWarning("BlendShape not found in avatar: " + blendShape.blendShape);
}
break;
case ScaleAction scaleAction:
if (scaleAction.obj == null) {
Debug.LogWarning("Missing object in action: " + name);
} else {
clipBuilder.Scale(clip, scaleAction.obj,
scaleAction.obj.transform.localScale.x * scaleAction.scale,
scaleAction.obj.transform.localScale.y * scaleAction.scale,
scaleAction.obj.transform.localScale.z * scaleAction.scale);
}
break;
case MaterialAction matAction:
if (matAction.obj == null) {
Debug.LogWarning("Missing object in action: " + name);
break;
}
if (matAction.mat == null) {
Debug.LogWarning("Missing material in action: " + name);
break;
}
clipBuilder.Material(clip, matAction.obj, matAction.materialIndex, matAction.mat);
break;
}
}
return clip;
}
public List<FeatureBuilderAction> GetActions() {
var list = new List<FeatureBuilderAction>();
foreach (var method in GetType().GetMethods()) {
var attr = method.GetCustomAttribute<FeatureBuilderActionAttribute>();
if (attr == null) continue;
list.Add(new FeatureBuilderAction(attr, method, this));
}
if (list.Count == 0) {
throw new Exception("Builder had no actions? This is probably a bug. " + GetType().Name);
}
return list;
}
}
public abstract class FeatureBuilder<ModelType> : FeatureBuilder where ModelType : FeatureModel {
public ModelType model;
}
}