mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-04-04 19:49:02 +08:00
Merge branch 'main' into vrm
# Conflicts: # Editor/nadena.dev.modular-avatar.core.editor.asmdef
This commit is contained in:
commit
88ee50977b
2
.github/ProjectRoot/vpm-manifest-2022.json
vendored
2
.github/ProjectRoot/vpm-manifest-2022.json
vendored
@ -19,7 +19,7 @@
|
||||
"dependencies": {}
|
||||
},
|
||||
"nadena.dev.ndmf": {
|
||||
"version": "1.5.0-rc.3"
|
||||
"version": "1.5.0-rc.11"
|
||||
}
|
||||
}
|
||||
}
|
2
.github/workflows/deploy-pages.yml
vendored
2
.github/workflows/deploy-pages.yml
vendored
@ -122,7 +122,7 @@ jobs:
|
||||
workingDirectory: docs-site~
|
||||
|
||||
- name: Purge cache
|
||||
uses: nathanvaughn/actions-cloudflare-purge@367672c723960cd03bb7d8c2c4d89062a3fc1fac
|
||||
uses: nathanvaughn/actions-cloudflare-purge@aa1121a867565ea71b60f445f441544df0c7b0b9
|
||||
continue-on-error: true
|
||||
with:
|
||||
cf_zone: ${{ secrets.CF_ZONE_ID }}
|
||||
|
@ -9,6 +9,7 @@ using nadena.dev.ndmf;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using BuildContext = nadena.dev.ndmf.BuildContext;
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
@ -97,11 +98,13 @@ namespace nadena.dev.modular_avatar.animation
|
||||
|
||||
internal void Commit()
|
||||
{
|
||||
Profiler.BeginSample("AnimationDatabase.Commit");
|
||||
foreach (var clip in _clips)
|
||||
{
|
||||
if (clip.IsProxyAnimation) clip.CurrentClip = clip.OriginalClip;
|
||||
}
|
||||
|
||||
Profiler.BeginSample("UpdateClipProperties");
|
||||
foreach (var clip in _clips)
|
||||
{
|
||||
// Changing the "high quality curve" setting can result in behavior changes (but can happen accidentally
|
||||
@ -121,11 +124,16 @@ namespace nadena.dev.modular_avatar.animation
|
||||
}
|
||||
}
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
Profiler.BeginSample("ClipCommitActions");
|
||||
foreach (var action in _clipCommitActions)
|
||||
{
|
||||
action();
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
internal void OnActivate(BuildContext context)
|
||||
@ -194,7 +202,11 @@ namespace nadena.dev.modular_avatar.animation
|
||||
var clipHolder = RegisterMotion(state.motion, state, processClip, _originalToHolder);
|
||||
state.motion = clipHolder.CurrentClip;
|
||||
|
||||
_clipCommitActions.Add(() => { state.motion = clipHolder.CurrentClip; });
|
||||
_clipCommitActions.Add(() =>
|
||||
{
|
||||
state.motion = clipHolder.CurrentClip;
|
||||
MaybeSaveClip(clipHolder.CurrentClip);
|
||||
});
|
||||
}
|
||||
|
||||
internal void ForeachClip(Action<ClipHolder> processClip)
|
||||
@ -370,6 +382,8 @@ namespace nadena.dev.modular_avatar.animation
|
||||
children[i].motion = curClip;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
MaybeSaveClip(curClip);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
@ -381,5 +395,23 @@ namespace nadena.dev.modular_avatar.animation
|
||||
|
||||
return treeHolder;
|
||||
}
|
||||
|
||||
private void MaybeSaveClip(Motion curClip)
|
||||
{
|
||||
Profiler.BeginSample("MaybeSaveClip");
|
||||
if (curClip != null && !EditorUtility.IsPersistent(curClip) && EditorUtility.IsPersistent(_context.AssetContainer) && _context.AssetContainer != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssetDatabase.AddObjectToAsset(curClip, _context.AssetContainer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
}
|
@ -42,7 +42,9 @@ namespace nadena.dev.modular_avatar.animation
|
||||
throw new Exception("Unknown RuntimeAnimatorContoller type " + controller.GetType());
|
||||
}
|
||||
|
||||
return merger.Finish();
|
||||
var clone = merger.Finish();
|
||||
ObjectRegistry.RegisterReplacedObject(controller, clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
internal static void CloneAllControllers(BuildContext context)
|
||||
|
@ -473,7 +473,7 @@ namespace nadena.dev.modular_avatar.animation
|
||||
var newLayer = new AnimatorControllerLayer()
|
||||
{
|
||||
name = layer.name,
|
||||
avatarMask = layer.avatarMask, // TODO map transforms
|
||||
avatarMask = _deepClone.DoClone(layer.avatarMask, basePath, _cloneMap),
|
||||
blendingMode = layer.blendingMode,
|
||||
defaultWeight = first ? 1 : layer.defaultWeight,
|
||||
syncedLayerIndex = layer.syncedLayerIndex,
|
||||
@ -494,7 +494,8 @@ namespace nadena.dev.modular_avatar.animation
|
||||
var overrideMotion = layer.GetOverrideMotion(state);
|
||||
if (overrideMotion != null)
|
||||
{
|
||||
newLayer.SetOverrideMotion((AnimatorState)_cloneMap[state], overrideMotion);
|
||||
var newMotion = _deepClone.DoClone(overrideMotion, basePath, _cloneMap);
|
||||
newLayer.SetOverrideMotion((AnimatorState)_cloneMap[state], newMotion);
|
||||
}
|
||||
|
||||
var overrideBehaviors = (StateMachineBehaviour[])layer.GetOverrideBehaviours(state)?.Clone();
|
||||
|
@ -50,6 +50,7 @@ namespace nadena.dev.modular_avatar.animation
|
||||
case AnimatorStateMachine _:
|
||||
case AnimatorTransitionBase _:
|
||||
case StateMachineBehaviour _:
|
||||
case AvatarMask _:
|
||||
break; // We want to clone these types
|
||||
|
||||
case AudioClip _: //Used in VRC Animator Play Audio State Behavior
|
||||
@ -92,10 +93,17 @@ namespace nadena.dev.modular_avatar.animation
|
||||
{
|
||||
ObjectRegistry.RegisterReplacedObject(original, obj);
|
||||
}
|
||||
|
||||
if (_isSaved && !EditorUtility.IsPersistent(obj))
|
||||
{
|
||||
AssetDatabase.AddObjectToAsset(obj, _combined);
|
||||
}
|
||||
|
||||
return (T)obj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var ctor = original.GetType().GetConstructor(Type.EmptyTypes);
|
||||
if (ctor == null || original is ScriptableObject)
|
||||
{
|
||||
@ -146,8 +154,74 @@ namespace nadena.dev.modular_avatar.animation
|
||||
return (T)obj;
|
||||
}
|
||||
|
||||
// internal for testing
|
||||
internal static AvatarMask CloneAvatarMask(AvatarMask mask, string basePath)
|
||||
{
|
||||
if (basePath.EndsWith("/")) basePath = basePath.Substring(0, basePath.Length - 1);
|
||||
|
||||
var newMask = new AvatarMask();
|
||||
|
||||
// Transfer first the humanoid mask data
|
||||
EditorUtility.CopySerialized(mask, newMask);
|
||||
|
||||
var srcSo = new SerializedObject(mask);
|
||||
var dstSo = new SerializedObject(newMask);
|
||||
var srcElements = srcSo.FindProperty("m_Elements");
|
||||
|
||||
if (basePath == "" || srcElements.arraySize == 0) return newMask; // no changes required
|
||||
|
||||
// We now need to prefix the elements of basePath (with weight zero)
|
||||
|
||||
var newElements = new List<string>();
|
||||
|
||||
var accum = "";
|
||||
foreach (var element in basePath.Split("/"))
|
||||
{
|
||||
if (accum != "") accum += "/";
|
||||
accum += element;
|
||||
|
||||
newElements.Add(accum);
|
||||
}
|
||||
|
||||
var dstElements = dstSo.FindProperty("m_Elements");
|
||||
|
||||
// We'll need to create new array elements by using DuplicateCommand. We'll then rewrite the whole
|
||||
// list to keep things in traversal order.
|
||||
for (var i = 0; i < newElements.Count; i++) dstElements.GetArrayElementAtIndex(0).DuplicateCommand();
|
||||
|
||||
var totalElements = srcElements.arraySize + newElements.Count;
|
||||
for (var i = 0; i < totalElements; i++)
|
||||
{
|
||||
var dstElem = dstElements.GetArrayElementAtIndex(i);
|
||||
var dstPath = dstElem.FindPropertyRelative("m_Path");
|
||||
var dstWeight = dstElem.FindPropertyRelative("m_Weight");
|
||||
|
||||
var srcIndex = i - newElements.Count;
|
||||
if (srcIndex < 0)
|
||||
{
|
||||
dstPath.stringValue = newElements[i];
|
||||
dstWeight.floatValue = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var srcElem = srcElements.GetArrayElementAtIndex(srcIndex);
|
||||
dstPath.stringValue = basePath + "/" + srcElem.FindPropertyRelative("m_Path").stringValue;
|
||||
dstWeight.floatValue = srcElem.FindPropertyRelative("m_Weight").floatValue;
|
||||
}
|
||||
}
|
||||
|
||||
dstSo.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
return newMask;
|
||||
}
|
||||
|
||||
private UnityObject CloneWithPathMapping(UnityObject o, string basePath)
|
||||
{
|
||||
if (o is AvatarMask mask)
|
||||
{
|
||||
return CloneAvatarMask(mask, basePath);
|
||||
}
|
||||
|
||||
if (o is AnimationClip clip)
|
||||
{
|
||||
// We'll always rebase if the asset is non-persistent, because we can't reference a nonpersistent asset
|
||||
|
@ -6,7 +6,9 @@ using System.Linq;
|
||||
using nadena.dev.ndmf;
|
||||
using nadena.dev.ndmf.util;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER
|
||||
#endif
|
||||
|
||||
@ -278,10 +280,68 @@ namespace nadena.dev.modular_avatar.animation
|
||||
return newClip;
|
||||
}
|
||||
|
||||
private void ApplyMappingsToAvatarMask(AvatarMask mask)
|
||||
{
|
||||
if (mask == null) return;
|
||||
|
||||
var maskSo = new SerializedObject(mask);
|
||||
|
||||
var seenTransforms = new Dictionary<string, float>();
|
||||
var transformOrder = new List<string>();
|
||||
var m_Elements = maskSo.FindProperty("m_Elements");
|
||||
var elementCount = m_Elements.arraySize;
|
||||
|
||||
for (var i = 0; i < elementCount; i++)
|
||||
{
|
||||
var element = m_Elements.GetArrayElementAtIndex(i);
|
||||
var path = element.FindPropertyRelative("m_Path").stringValue;
|
||||
var weight = element.FindPropertyRelative("m_Weight").floatValue;
|
||||
|
||||
path = MapPath(path);
|
||||
|
||||
// ensure all parent elements are present
|
||||
EnsureParentsPresent(path);
|
||||
|
||||
if (!seenTransforms.ContainsKey(path)) transformOrder.Add(path);
|
||||
seenTransforms[path] = weight;
|
||||
}
|
||||
|
||||
transformOrder.Sort();
|
||||
m_Elements.arraySize = transformOrder.Count;
|
||||
|
||||
for (var i = 0; i < transformOrder.Count; i++)
|
||||
{
|
||||
var element = m_Elements.GetArrayElementAtIndex(i);
|
||||
var path = transformOrder[i];
|
||||
|
||||
element.FindPropertyRelative("m_Path").stringValue = path;
|
||||
element.FindPropertyRelative("m_Weight").floatValue = seenTransforms[path];
|
||||
}
|
||||
|
||||
maskSo.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
void EnsureParentsPresent(string path)
|
||||
{
|
||||
var nextSlash = -1;
|
||||
|
||||
while ((nextSlash = path.IndexOf('/', nextSlash + 1)) != -1)
|
||||
{
|
||||
var parentPath = path.Substring(0, nextSlash);
|
||||
if (!seenTransforms.ContainsKey(parentPath))
|
||||
{
|
||||
seenTransforms[parentPath] = 0;
|
||||
transformOrder.Add(parentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnDeactivate(BuildContext context)
|
||||
{
|
||||
Profiler.BeginSample("PathMappings.OnDeactivate");
|
||||
Dictionary<AnimationClip, AnimationClip> clipCache = new Dictionary<AnimationClip, AnimationClip>();
|
||||
|
||||
|
||||
Profiler.BeginSample("ApplyMappingsToClip");
|
||||
_animationDatabase.ForeachClip(holder =>
|
||||
{
|
||||
if (holder.CurrentClip is AnimationClip clip)
|
||||
@ -289,19 +349,42 @@ namespace nadena.dev.modular_avatar.animation
|
||||
holder.CurrentClip = ApplyMappingsToClip(clip, clipCache);
|
||||
}
|
||||
});
|
||||
Profiler.EndSample();
|
||||
|
||||
#if MA_VRCSDK3_AVATARS_3_5_2_OR_NEWER
|
||||
Profiler.BeginSample("MapPlayAudio");
|
||||
_animationDatabase.ForeachPlayAudio(playAudio =>
|
||||
{
|
||||
if (playAudio == null) return;
|
||||
playAudio.SourcePath = MapPath(playAudio.SourcePath, true);
|
||||
});
|
||||
Profiler.EndSample();
|
||||
#endif
|
||||
|
||||
Profiler.BeginSample("InvokeIOnCommitObjectRenamesCallbacks");
|
||||
foreach (var listener in context.AvatarRootObject.GetComponentsInChildren<IOnCommitObjectRenames>())
|
||||
{
|
||||
listener.OnCommitObjectRenames(context, this);
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
var layers = context.AvatarDescriptor.baseAnimationLayers
|
||||
.Concat(context.AvatarDescriptor.specialAnimationLayers);
|
||||
|
||||
Profiler.BeginSample("ApplyMappingsToAvatarMasks");
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
ApplyMappingsToAvatarMask(layer.mask);
|
||||
|
||||
if (layer.animatorController is AnimatorController ac)
|
||||
// By this point, all AnimationOverrideControllers have been collapsed into an ephemeral
|
||||
// AnimatorController so we can safely modify the controller in-place.
|
||||
foreach (var acLayer in ac.layers)
|
||||
ApplyMappingsToAvatarMask(acLayer.avatarMask);
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
public GameObject PathToObject(string path)
|
||||
|
@ -40,7 +40,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
switch (parameters[i].type)
|
||||
{
|
||||
case AnimatorControllerParameterType.Bool:
|
||||
parameters[i].defaultBool = defaultValue > 0.5f;
|
||||
parameters[i].defaultBool = defaultValue != 0.0f;
|
||||
break;
|
||||
case AnimatorControllerParameterType.Int:
|
||||
parameters[i].defaultInt = Mathf.RoundToInt(defaultValue);
|
||||
|
@ -36,17 +36,17 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftLowerLeg", "LowerLeg_Left", "LowerLeg_L", "Knee_Left", "Knee_L", "LLeg_L", "Left knee", "LeftLeg", "leg_L"
|
||||
"LeftLowerLeg", "LowerLeg_Left", "LowerLeg_L", "Knee_Left", "Knee_L", "LLeg_L", "Left knee", "LeftLeg", "leg_L", "shin.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightLowerLeg", "LowerLeg_Right", "LowerLeg_R", "Knee_Right", "Knee_R", "LLeg_R", "Right knee",
|
||||
"RightLeg", "leg_R"
|
||||
"RightLeg", "leg_R", "shin.R"
|
||||
},
|
||||
new[] {"LeftFoot", "Foot_Left", "Foot_L", "Ankle_L", "Foot.L.001", "Left ankle", "heel.L", "heel"},
|
||||
new[] {"RightFoot", "Foot_Right", "Foot_R", "Ankle_R", "Foot.R.001", "Right ankle", "heel.R", "heel"},
|
||||
new[] {"Spine", "spine01"},
|
||||
new[] {"Chest", "Bust", "spine02"},
|
||||
new[] {"Chest", "Bust", "spine02", "upper_chest"},
|
||||
new[] {"Neck"},
|
||||
new[] {"Head"},
|
||||
new[] {"LeftShoulder", "Shoulder_Left", "Shoulder_L"},
|
||||
@ -95,62 +95,62 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
new[]
|
||||
{
|
||||
"LeftIndexProximal", "ProximalIndex_Left", "ProximalIndex_L", "Index1_L", "IndexFinger1_L",
|
||||
"LeftHandIndex1", "Index Proximal.L", "finger02_01_L"
|
||||
"LeftHandIndex1", "Index Proximal.L", "finger02_01_L", "f_index.01.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftIndexIntermediate", "IntermediateIndex_Left", "IntermediateIndex_L", "Index2_L", "IndexFinger2_L",
|
||||
"LeftHandIndex2", "Index Intermediate.L", "finger02_02_L"
|
||||
"LeftHandIndex2", "Index Intermediate.L", "finger02_02_L", "f_index.02.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftIndexDistal", "DistalIndex_Left", "DistalIndex_L", "Index3_L", "IndexFinger3_L", "LeftHandIndex3",
|
||||
"Index Distal.L", "finger02_03_L"
|
||||
"Index Distal.L", "finger02_03_L", "f_index.03.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftMiddleProximal", "ProximalMiddle_Left", "ProximalMiddle_L", "Middle1_L", "MiddleFinger1_L",
|
||||
"LeftHandMiddle1", "Middle Proximal.L", "finger03_01_L"
|
||||
"LeftHandMiddle1", "Middle Proximal.L", "finger03_01_L", "f_middle.01.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftMiddleIntermediate", "IntermediateMiddle_Left", "IntermediateMiddle_L", "Middle2_L",
|
||||
"MiddleFinger2_L", "LeftHandMiddle2", "Middle Intermediate.L", "finger03_02_L"
|
||||
"MiddleFinger2_L", "LeftHandMiddle2", "Middle Intermediate.L", "finger03_02_L", "f_middle.02.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftMiddleDistal", "DistalMiddle_Left", "DistalMiddle_L", "Middle3_L", "MiddleFinger3_L",
|
||||
"LeftHandMiddle3", "Middle Distal.L", "finger03_03_L"
|
||||
"LeftHandMiddle3", "Middle Distal.L", "finger03_03_L", "f_middle.03.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftRingProximal", "ProximalRing_Left", "ProximalRing_L", "Ring1_L", "RingFinger1_L", "LeftHandRing1",
|
||||
"Ring Proximal.L", "finger04_01_L"
|
||||
"Ring Proximal.L", "finger04_01_L", "f_ring.01.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftRingIntermediate", "IntermediateRing_Left", "IntermediateRing_L", "Ring2_L", "RingFinger2_L",
|
||||
"LeftHandRing2", "Ring Intermediate.L", "finger04_02_L"
|
||||
"LeftHandRing2", "Ring Intermediate.L", "finger04_02_L", "f_ring.02.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftRingDistal", "DistalRing_Left", "DistalRing_L", "Ring3_L", "RingFinger3_L", "LeftHandRing3",
|
||||
"Ring Distal.L", "finger04_03_L"
|
||||
"Ring Distal.L", "finger04_03_L", "f_ring.03.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftLittleProximal", "ProximalLittle_Left", "ProximalLittle_L", "Little1_L", "LittleFinger1_L",
|
||||
"LeftHandPinky1", "Little Proximal.L", "finger05_01_L"
|
||||
"LeftHandPinky1", "Little Proximal.L", "finger05_01_L", "f_pinky.01.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftLittleIntermediate", "IntermediateLittle_Left", "IntermediateLittle_L", "Little2_L",
|
||||
"LittleFinger2_L", "LeftHandPinky2", "Little Intermediate.L", "finger05_02_L"
|
||||
"LittleFinger2_L", "LeftHandPinky2", "Little Intermediate.L", "finger05_02_L", "f_pinky.02.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"LeftLittleDistal", "DistalLittle_Left", "DistalLittle_L", "Little3_L", "LittleFinger3_L",
|
||||
"LeftHandPinky3", "Little Distal.L", "finger05_03_L"
|
||||
"LeftHandPinky3", "Little Distal.L", "finger05_03_L", "f_pinky.03.L"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
@ -170,62 +170,62 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
new[]
|
||||
{
|
||||
"RightIndexProximal", "ProximalIndex_Right", "ProximalIndex_R", "Index1_R", "IndexFinger1_R",
|
||||
"RightHandIndex1", "Index Proximal.R", "finger02_01_R"
|
||||
"RightHandIndex1", "Index Proximal.R", "finger02_01_R", "f_index.01.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightIndexIntermediate", "IntermediateIndex_Right", "IntermediateIndex_R", "Index2_R",
|
||||
"IndexFinger2_R", "RightHandIndex2", "Index Intermediate.R", "finger02_02_R"
|
||||
"IndexFinger2_R", "RightHandIndex2", "Index Intermediate.R", "finger02_02_R", "f_index.02.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightIndexDistal", "DistalIndex_Right", "DistalIndex_R", "Index3_R", "IndexFinger3_R",
|
||||
"RightHandIndex3", "Index Distal.R", "finger02_03_R"
|
||||
"RightHandIndex3", "Index Distal.R", "finger02_03_R", "f_index.03.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightMiddleProximal", "ProximalMiddle_Right", "ProximalMiddle_R", "Middle1_R", "MiddleFinger1_R",
|
||||
"RightHandMiddle1", "Middle Proximal.R", "finger03_01_R"
|
||||
"RightHandMiddle1", "Middle Proximal.R", "finger03_01_R", "f_middle.01.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightMiddleIntermediate", "IntermediateMiddle_Right", "IntermediateMiddle_R", "Middle2_R",
|
||||
"MiddleFinger2_R", "RightHandMiddle2", "Middle Intermediate.R", "finger03_02_R"
|
||||
"MiddleFinger2_R", "RightHandMiddle2", "Middle Intermediate.R", "finger03_02_R", "f_middle.02.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightMiddleDistal", "DistalMiddle_Right", "DistalMiddle_R", "Middle3_R", "MiddleFinger3_R",
|
||||
"RightHandMiddle3", "Middle Distal.R", "finger03_03_R"
|
||||
"RightHandMiddle3", "Middle Distal.R", "finger03_03_R", "f_middle.03.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightRingProximal", "ProximalRing_Right", "ProximalRing_R", "Ring1_R", "RingFinger1_R",
|
||||
"RightHandRing1", "Ring Proximal.R", "finger04_01_R"
|
||||
"RightHandRing1", "Ring Proximal.R", "finger04_01_R", "f_ring.01.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightRingIntermediate", "IntermediateRing_Right", "IntermediateRing_R", "Ring2_R", "RingFinger2_R",
|
||||
"RightHandRing2", "Ring Intermediate.R", "finger04_02_R"
|
||||
"RightHandRing2", "Ring Intermediate.R", "finger04_02_R", "f_ring.02.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightRingDistal", "DistalRing_Right", "DistalRing_R", "Ring3_R", "RingFinger3_R", "RightHandRing3",
|
||||
"Ring Distal.R", "finger04_03_R"
|
||||
"Ring Distal.R", "finger04_03_R", "f_ring.03.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightLittleProximal", "ProximalLittle_Right", "ProximalLittle_R", "Little1_R", "LittleFinger1_R",
|
||||
"RightHandPinky1", "Little Proximal.R", "finger05_01_R"
|
||||
"RightHandPinky1", "Little Proximal.R", "finger05_01_R", "f_pinky.01.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightLittleIntermediate", "IntermediateLittle_Right", "IntermediateLittle_R", "Little2_R",
|
||||
"LittleFinger2_R", "RightHandPinky2", "Little Intermediate.R", "finger05_02_R"
|
||||
"LittleFinger2_R", "RightHandPinky2", "Little Intermediate.R", "finger05_02_R", "f_pinky.02.R"
|
||||
},
|
||||
new[]
|
||||
{
|
||||
"RightLittleDistal", "DistalLittle_Right", "DistalLittle_R", "Little3_R", "LittleFinger3_R",
|
||||
"RightHandPinky3", "Little Distal.R", "finger05_03_R"
|
||||
"RightHandPinky3", "Little Distal.R", "finger05_03_R", "f_pinky.03.R"
|
||||
},
|
||||
new[] {"UpperChest", "UChest"},
|
||||
};
|
||||
|
@ -95,7 +95,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var t = (ModularAvatarBoneProxy) targets[i];
|
||||
Undo.RecordObjects(targets, "Set targets");
|
||||
var xform = ((TempObjRef) objRefs[i]).target;
|
||||
if (RuntimeUtil.FindAvatarTransformInParents(xform)?.gameObject != parentAvatar) continue;
|
||||
if (xform != null && RuntimeUtil.FindAvatarTransformInParents(xform)?.gameObject != parentAvatar) continue;
|
||||
t.target = xform;
|
||||
}
|
||||
}
|
||||
@ -159,4 +159,4 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
case VisibleHeadAccessoryValidation.ReadyStatus.ParentMarked:
|
||||
EditorGUILayout.HelpBox(Localization.S("fpvisible.normal"), MessageType.Info);
|
||||
break;
|
||||
case VisibleHeadAccessoryValidation.ReadyStatus.NotUnderHead:
|
||||
EditorGUILayout.HelpBox(Localization.S("fpvisible.NotUnderHead"), MessageType.Warning);
|
||||
break;
|
||||
default:
|
||||
{
|
||||
var label = "fpvisible." + status;
|
||||
@ -49,4 +52,4 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
Localization.ShowLanguageUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
return (EditorStyles.label?.lineHeight ?? 0) * 3;
|
||||
}
|
||||
catch (NullReferenceException e)
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
// This can happen in early initialization...
|
||||
return 0;
|
||||
|
@ -19,7 +19,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
EditorGUILayout.HelpBox("Unable to show override changes", MessageType.Info);
|
||||
}
|
||||
|
||||
protected override VisualElement CreateInnerInspectorGUI()
|
||||
|
@ -1,7 +1,4 @@
|
||||
VisualElement {
|
||||
}
|
||||
|
||||
#group-box {
|
||||
#group-box {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
padding: 4px;
|
||||
@ -14,21 +11,36 @@
|
||||
/* background-color: rgba(0, 0, 0, 0.1); */
|
||||
}
|
||||
|
||||
#ListViewContainer {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#group-box > Label {
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
flex-direction: row;
|
||||
#ListViewContainer {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.horizontal #f-object {
|
||||
.horizontal {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
.horizontal > * {
|
||||
height: 18px;
|
||||
margin: 0 1px;
|
||||
}
|
||||
|
||||
.horizontal > Label {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.horizontal > PropertyField > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#f-object {
|
||||
flex-grow: 1;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
#f-material-index {
|
||||
@ -43,14 +55,10 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.horizontal > Label {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#f-material {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.horizontal > Label {
|
||||
align-self: center;
|
||||
width: 100px;
|
||||
height: 19px;
|
||||
margin: 1px -2px 1px 3px;
|
||||
-unity-text-align: middle-left;
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
#endregion
|
||||
@ -148,7 +147,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
{
|
||||
return targetObject?.GetComponent<Renderer>()?.sharedMaterials;
|
||||
}
|
||||
catch (MissingComponentException e)
|
||||
catch (MissingComponentException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
|
||||
<ui:VisualElement class="toggled-object-editor">
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ed:PropertyField binding-path="Object" label="" name="f-object" class="f-object"/>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ed:IntegerField name="f-material-index" binding-path="MaterialIndex"/>
|
||||
<ui:DropdownField name="f-material-index-dropdown"/>
|
||||
<ed:ObjectField name="f-material-index-original"/>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ui:Label text="reactive_object.material-setter.set-to" class="ndmf-tr"/>
|
||||
<ed:PropertyField binding-path="Material" label="" name="f-material"/>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ed:PropertyField name="f-object" binding-path="Object" label=""/>
|
||||
</ui:VisualElement>
|
||||
</UXML>
|
||||
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ed:IntegerField name="f-material-index" binding-path="MaterialIndex"/>
|
||||
<ui:DropdownField name="f-material-index-dropdown"/>
|
||||
<ed:ObjectField name="f-material-index-original"/>
|
||||
</ui:VisualElement>
|
||||
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ui:Label text="reactive_object.material-setter.set-to" class="ndmf-tr"/>
|
||||
<ed:PropertyField name="f-material" binding-path="Material" label=""/>
|
||||
</ui:VisualElement>
|
||||
</UXML>
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using nadena.dev.modular_avatar.core.menu;
|
||||
using nadena.dev.ndmf;
|
||||
using nadena.dev.ndmf.preview;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
@ -21,6 +23,40 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
protected override string localizationPrefix => "submenu_source";
|
||||
}
|
||||
|
||||
internal static class ParameterIntrospectionCache
|
||||
{
|
||||
internal static PropCache<GameObject, ImmutableList<ProvidedParameter>> ProvidedParameterCache =
|
||||
new("GetParametersForObject", GetParametersForObject_miss);
|
||||
|
||||
internal static PropCache<GameObject, ImmutableDictionary<(ParameterNamespace, string), ParameterMapping>>
|
||||
ParameterRemappingCache = new("GetParameterRemappingsAt", GetParameterRemappingsAt_miss);
|
||||
|
||||
private static ImmutableList<ProvidedParameter> GetParametersForObject_miss(ComputeContext ctx, GameObject obj)
|
||||
{
|
||||
if (obj == null) return ImmutableList<ProvidedParameter>.Empty;
|
||||
|
||||
return ParameterInfo.ForPreview(ctx).GetParametersForObject(obj).ToImmutableList();
|
||||
}
|
||||
|
||||
private static ImmutableDictionary<(ParameterNamespace, string), ParameterMapping>
|
||||
GetParameterRemappingsAt_miss(ComputeContext ctx, GameObject obj)
|
||||
{
|
||||
if (obj == null) return ImmutableDictionary<(ParameterNamespace, string), ParameterMapping>.Empty;
|
||||
|
||||
return ParameterInfo.ForPreview(ctx).GetParameterRemappingsAt(obj);
|
||||
}
|
||||
|
||||
internal static ImmutableList<ProvidedParameter> GetParametersForObject(GameObject avatar)
|
||||
{
|
||||
return ProvidedParameterCache.Get(ComputeContext.NullContext, avatar);
|
||||
}
|
||||
|
||||
internal static ImmutableDictionary<(ParameterNamespace, string), ParameterMapping> GetParameterRemappingsAt(GameObject avatar)
|
||||
{
|
||||
return ParameterRemappingCache.Get(ComputeContext.NullContext, avatar);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MenuItemCoreGUI
|
||||
{
|
||||
private static readonly ObjectIDGenerator IdGenerator = new ObjectIDGenerator();
|
||||
@ -54,6 +90,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
private readonly SerializedProperty _prop_isSynced;
|
||||
private readonly SerializedProperty _prop_isSaved;
|
||||
private readonly SerializedProperty _prop_isDefault;
|
||||
private readonly SerializedProperty _prop_automaticValue;
|
||||
|
||||
public bool AlwaysExpandContents = false;
|
||||
public bool ExpandContents = false;
|
||||
@ -105,6 +142,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
_prop_isSynced = obj.FindProperty(nameof(ModularAvatarMenuItem.isSynced));
|
||||
_prop_isSaved = obj.FindProperty(nameof(ModularAvatarMenuItem.isSaved));
|
||||
_prop_isDefault = obj.FindProperty(nameof(ModularAvatarMenuItem.isDefault));
|
||||
_prop_automaticValue = obj.FindProperty(nameof(ModularAvatarMenuItem.automaticValue));
|
||||
|
||||
_previewGUI = new MenuPreviewGUI(redraw);
|
||||
}
|
||||
@ -132,12 +170,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
Dictionary<string, ProvidedParameter> rootParameters = new();
|
||||
|
||||
foreach (var param in ParameterInfo.ForUI.GetParametersForObject(parentAvatar.gameObject)
|
||||
foreach (var param in ParameterIntrospectionCache.GetParametersForObject(parentAvatar.gameObject)
|
||||
.Where(p => p.Namespace == ParameterNamespace.Animator)
|
||||
)
|
||||
rootParameters[param.EffectiveName] = param;
|
||||
|
||||
var remaps = ParameterInfo.ForUI.GetParameterRemappingsAt(paramRef);
|
||||
var remaps = ParameterIntrospectionCache.GetParameterRemappingsAt(paramRef);
|
||||
foreach (var remap in remaps)
|
||||
{
|
||||
if (remap.Key.Item1 != ParameterNamespace.Animator) continue;
|
||||
@ -180,6 +218,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
_prop_isSynced = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSynced));
|
||||
_prop_isSaved = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isSaved));
|
||||
_prop_isDefault = _control.FindPropertyRelative(nameof(ModularAvatarMenuItem.isDefault));
|
||||
_prop_automaticValue = null;
|
||||
|
||||
_prop_submenuSource = null;
|
||||
_prop_otherObjSource = null;
|
||||
@ -202,22 +241,17 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
if (forceMixedValues != null) EditorGUI.showMixedValue = forceMixedValues.Value;
|
||||
|
||||
if (forceValue != null)
|
||||
{
|
||||
EditorGUI.ToggleLeft(rect, label, forceValue.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var value = EditorGUI.ToggleLeft(rect, label, prop.boolValue);
|
||||
if (EditorGUI.EndChangeCheck()) prop.boolValue = value;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var value = EditorGUI.ToggleLeft(rect, label, forceValue ?? prop.boolValue);
|
||||
if (EditorGUI.EndChangeCheck()) prop.boolValue = value;
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
public void DoGUI()
|
||||
{
|
||||
if (_obj != null) _obj.UpdateIfRequiredOrScript();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
@ -230,7 +264,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
EditorGUILayout.PropertyField(_texture, G("menuitem.prop.icon"));
|
||||
EditorGUILayout.PropertyField(_type, G("menuitem.prop.type"));
|
||||
EditorGUILayout.PropertyField(_value, G("menuitem.prop.value"));
|
||||
DoValueField();
|
||||
|
||||
_parameterGUI.DoGUI(true);
|
||||
|
||||
@ -436,22 +470,44 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
// For now, don't show the UI in this case.
|
||||
return;
|
||||
|
||||
var multipleSelections = _obj.targetObjects.Length > 1;
|
||||
|
||||
var paramName = _parameterName.stringValue;
|
||||
var siblings = FindSiblingMenuItems(_obj);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
bool? forceMixedValues = _parameterName.hasMultipleDifferentValues ? true : null;
|
||||
var forceMixedValues = _parameterName.hasMultipleDifferentValues;
|
||||
|
||||
var syncedIsMixed = forceMixedValues || _prop_isSynced.hasMultipleDifferentValues ||
|
||||
siblings != null && siblings.Any(s => s.isSynced != _prop_isSynced.boolValue);
|
||||
var savedIsMixed = forceMixedValues || _prop_isSaved.hasMultipleDifferentValues ||
|
||||
siblings != null && siblings.Any(s => s.isSaved != _prop_isSaved.boolValue);
|
||||
|
||||
var knownParameter = _parameterName.hasMultipleDifferentValues
|
||||
? null
|
||||
: _knownParameters.GetValueOrDefault(paramName);
|
||||
|
||||
var knownSource = knownParameter?.Source;
|
||||
var externalSource = knownSource != null && knownSource is not ModularAvatarMenuItem;
|
||||
|
||||
if (externalSource) savedIsMixed = true; // NDMF doesn't yet support querying for the saved state
|
||||
var forceSyncedValue = externalSource ? knownParameter?.WantSynced : null;
|
||||
|
||||
var knownParamDefault = knownParameter?.DefaultValue;
|
||||
var isDefaultByKnownParam =
|
||||
knownParamDefault != null ? _value.floatValue == knownParamDefault : (bool?)null;
|
||||
|
||||
if (knownParameter != null && knownParameter.Source is ModularAvatarMenuItem)
|
||||
isDefaultByKnownParam = null;
|
||||
|
||||
if (_prop_automaticValue?.boolValue == true) isDefaultByKnownParam = null;
|
||||
|
||||
Object controller = knownParameter?.Source;
|
||||
var controllerIsElsewhere = controller != null && !(controller is ModularAvatarMenuItem);
|
||||
// If we can't figure out what to reference the parameter names to, disable the UI
|
||||
controllerIsElsewhere = controllerIsElsewhere || _parameterSourceNotDetermined;
|
||||
|
||||
// If we can't figure out what to reference the parameter names to, or if they're controlled by something
|
||||
// other than the Menu Item component itself, disable the UI
|
||||
var controllerIsElsewhere = externalSource || _parameterSourceNotDetermined;
|
||||
|
||||
using (new EditorGUI.DisabledScope(
|
||||
_parameterName.hasMultipleDifferentValues || controllerIsElsewhere)
|
||||
@ -460,22 +516,42 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
// If we have multiple menu items selected, it probably doesn't make sense to make them all default.
|
||||
// But, we do want to see if _any_ are default.
|
||||
var anyIsDefault = _prop_isDefault.hasMultipleDifferentValues || _prop_isDefault.boolValue;
|
||||
var multipleSelections = _obj.targetObjects.Length > 1;
|
||||
var mixedIsDefault = multipleSelections && anyIsDefault;
|
||||
using (new EditorGUI.DisabledScope(multipleSelections))
|
||||
using (new EditorGUI.DisabledScope(multipleSelections || isDefaultByKnownParam != null))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawHorizontalToggleProp(_prop_isDefault, G("menuitem.prop.is_default"), mixedIsDefault,
|
||||
multipleSelections ? false : isDefaultByKnownParam);
|
||||
isDefaultByKnownParam);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
_obj.ApplyModifiedProperties();
|
||||
ClearConflictingDefaults(siblings);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
var isSavedMixed = forceMixedValues ??
|
||||
(_parameterName.hasMultipleDifferentValues || controllerIsElsewhere ? true : null);
|
||||
DrawHorizontalToggleProp(_prop_isSaved, G("menuitem.prop.is_saved"), isSavedMixed);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawHorizontalToggleProp(_prop_isSaved, G("menuitem.prop.is_saved"), savedIsMixed);
|
||||
if (EditorGUI.EndChangeCheck() && siblings != null)
|
||||
foreach (var sibling in siblings)
|
||||
{
|
||||
sibling.isSaved = _prop_isSaved.boolValue;
|
||||
EditorUtility.SetDirty(sibling);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(sibling);
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
DrawHorizontalToggleProp(_prop_isSynced, G("menuitem.prop.is_synced"), forceMixedValues,
|
||||
knownParameter?.WantSynced);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
DrawHorizontalToggleProp(_prop_isSynced, G("menuitem.prop.is_synced"), syncedIsMixed,
|
||||
forceSyncedValue);
|
||||
if (EditorGUI.EndChangeCheck() && siblings != null)
|
||||
foreach (var sibling in siblings)
|
||||
{
|
||||
sibling.isSynced = _prop_isSynced.boolValue;
|
||||
EditorUtility.SetDirty(sibling);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(sibling);
|
||||
}
|
||||
}
|
||||
|
||||
if (controllerIsElsewhere)
|
||||
@ -514,6 +590,109 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DoValueField()
|
||||
{
|
||||
var value_label = G("menuitem.prop.value");
|
||||
var auto_label = G("menuitem.prop.automatic_value");
|
||||
|
||||
if (_prop_automaticValue == null)
|
||||
{
|
||||
EditorGUILayout.PropertyField(_value, value_label);
|
||||
return;
|
||||
}
|
||||
|
||||
var toggleSize = EditorStyles.toggle.CalcSize(new GUIContent());
|
||||
var autoLabelSize = EditorStyles.label.CalcSize(auto_label);
|
||||
|
||||
var style = EditorStyles.numberField;
|
||||
var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight, style);
|
||||
|
||||
var valueRect = rect;
|
||||
valueRect.xMax -= toggleSize.x + autoLabelSize.x + 4;
|
||||
|
||||
var autoRect = rect;
|
||||
autoRect.xMin = valueRect.xMax + 4;
|
||||
|
||||
var suppressValue = _prop_automaticValue.boolValue || _prop_automaticValue.hasMultipleDifferentValues;
|
||||
|
||||
using (new EditorGUI.DisabledScope(suppressValue))
|
||||
{
|
||||
if (suppressValue)
|
||||
{
|
||||
EditorGUI.TextField(valueRect, value_label, "", style);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.PropertyField(valueRect, _value, value_label);
|
||||
if (EditorGUI.EndChangeCheck()) _prop_automaticValue.boolValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.BeginProperty(autoRect, auto_label, _prop_automaticValue);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUI.showMixedValue = _prop_automaticValue.hasMultipleDifferentValues;
|
||||
var autoValue = EditorGUI.ToggleLeft(autoRect, auto_label, _prop_automaticValue.boolValue);
|
||||
|
||||
if (EditorGUI.EndChangeCheck()) _prop_automaticValue.boolValue = autoValue;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
private List<ModularAvatarMenuItem> FindSiblingMenuItems(SerializedObject serializedObject)
|
||||
{
|
||||
if (serializedObject == null || serializedObject.isEditingMultipleObjects) return null;
|
||||
|
||||
var myMenuItem = serializedObject.targetObject as ModularAvatarMenuItem;
|
||||
if (myMenuItem == null) return null;
|
||||
|
||||
var myParameterName = myMenuItem.Control.parameter.name;
|
||||
if (string.IsNullOrEmpty(myParameterName)) return new List<ModularAvatarMenuItem>();
|
||||
|
||||
var myMappings = ParameterIntrospectionCache.GetParameterRemappingsAt(myMenuItem.gameObject);
|
||||
if (myMappings.TryGetValue((ParameterNamespace.Animator, myParameterName), out var myReplacement))
|
||||
myParameterName = myReplacement.ParameterName;
|
||||
|
||||
var avatarRoot = RuntimeUtil.FindAvatarInParents(myMenuItem.gameObject.transform);
|
||||
var siblings = new List<ModularAvatarMenuItem>();
|
||||
|
||||
foreach (var otherMenuItem in avatarRoot.GetComponentsInChildren<ModularAvatarMenuItem>(true))
|
||||
{
|
||||
if (otherMenuItem == myMenuItem) continue;
|
||||
|
||||
var otherParameterName = otherMenuItem.Control.parameter.name;
|
||||
if (string.IsNullOrEmpty(otherParameterName)) continue;
|
||||
|
||||
var otherMappings = ParameterIntrospectionCache.GetParameterRemappingsAt(otherMenuItem.gameObject);
|
||||
if (otherMappings.TryGetValue((ParameterNamespace.Animator, otherParameterName),
|
||||
out var otherReplacement))
|
||||
otherParameterName = otherReplacement.ParameterName;
|
||||
|
||||
if (otherParameterName != myParameterName) continue;
|
||||
|
||||
siblings.Add(otherMenuItem);
|
||||
}
|
||||
|
||||
return siblings;
|
||||
}
|
||||
|
||||
private void ClearConflictingDefaults(List<ModularAvatarMenuItem> siblingItems)
|
||||
{
|
||||
var siblings = siblingItems;
|
||||
if (siblings == null) return;
|
||||
|
||||
foreach (var otherMenuItem in siblings)
|
||||
{
|
||||
if (otherMenuItem.isDefault)
|
||||
{
|
||||
Undo.RecordObject(otherMenuItem, "");
|
||||
otherMenuItem.isDefault = false;
|
||||
EditorUtility.SetDirty(otherMenuItem);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(otherMenuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureLabelCount(int i)
|
||||
{
|
||||
if (_labels == null || _labelsRoot.arraySize < i || _labels.Length < i)
|
||||
|
@ -212,7 +212,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var newChild = new GameObject();
|
||||
newChild.name = "New item";
|
||||
newChild.transform.SetParent(nodesUnder.root.transform, false);
|
||||
newChild.AddComponent<ModularAvatarMenuItem>();
|
||||
|
||||
var mami = newChild.AddComponent<ModularAvatarMenuItem>();
|
||||
mami.InitSettings();
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(newChild, "Added menu item");
|
||||
}
|
||||
|
||||
@ -223,13 +226,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
newChild.transform.SetParent(nodesUnder.root.transform, false);
|
||||
|
||||
var mami = newChild.AddComponent<ModularAvatarMenuItem>();
|
||||
mami.InitSettings();
|
||||
mami.Control = new VRCExpressionsMenu.Control()
|
||||
{
|
||||
type = VRCExpressionsMenu.Control.ControlType.Toggle,
|
||||
value = 1,
|
||||
};
|
||||
mami.isSaved = true;
|
||||
mami.isSynced = true;
|
||||
|
||||
newChild.AddComponent<ModularAvatarObjectToggle>();
|
||||
|
||||
|
@ -30,7 +30,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
createInstaller = false;
|
||||
}
|
||||
}
|
||||
catch (MissingComponentException e)
|
||||
catch (MissingComponentException)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
@ -40,7 +40,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var objToggle = toggle.AddComponent<ModularAvatarObjectToggle>();
|
||||
|
||||
toggle.transform.SetParent(parent, false);
|
||||
toggle.AddComponent<ModularAvatarMenuItem>().Control = new VRCExpressionsMenu.Control
|
||||
|
||||
var mami = toggle.AddComponent<ModularAvatarMenuItem>();
|
||||
mami.InitSettings();
|
||||
mami.Control = new VRCExpressionsMenu.Control
|
||||
{
|
||||
type = VRCExpressionsMenu.Control.ControlType.Toggle,
|
||||
name = "New Toggle",
|
||||
|
@ -23,7 +23,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
EditorGUILayout.HelpBox("Unable to show override changes", MessageType.Info);
|
||||
}
|
||||
|
||||
protected override VisualElement CreateInnerInspectorGUI()
|
||||
@ -61,7 +61,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var grouped = ctx.Observe(target,
|
||||
t => (t.GroupedBones ?? Array.Empty<GameObject>())
|
||||
.Select(obj => obj.transform)
|
||||
.ToHashSet(new ObjectIdentityComparer<Transform>()),
|
||||
.ToHashSet(),
|
||||
(x, y) => x.SetEquals(y)
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
@ -16,10 +17,11 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
[SerializeField] private StyleSheet uss;
|
||||
[SerializeField] private VisualTreeAsset uxml;
|
||||
|
||||
private DragAndDropManipulator _dragAndDropManipulator;
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
EditorGUILayout.HelpBox("Unable to show override changes", MessageType.Info);
|
||||
}
|
||||
|
||||
protected override VisualElement CreateInnerInspectorGUI()
|
||||
@ -33,11 +35,109 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
ROSimulatorButton.BindRefObject(root, target);
|
||||
|
||||
var listView = root.Q<ListView>("Shapes");
|
||||
_dragAndDropManipulator = new DragAndDropManipulator(listView)
|
||||
{
|
||||
TargetComponent = target as ModularAvatarObjectToggle
|
||||
};
|
||||
|
||||
listView.showBoundCollectionSize = false;
|
||||
listView.virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight;
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_dragAndDropManipulator != null)
|
||||
_dragAndDropManipulator.TargetComponent = target as ModularAvatarObjectToggle;
|
||||
}
|
||||
|
||||
private class DragAndDropManipulator : PointerManipulator
|
||||
{
|
||||
public ModularAvatarObjectToggle TargetComponent;
|
||||
private GameObject[] _nowDragging = Array.Empty<GameObject>();
|
||||
private Transform _avatarRoot;
|
||||
|
||||
private readonly VisualElement _parentElem;
|
||||
|
||||
public DragAndDropManipulator(VisualElement target)
|
||||
{
|
||||
this.target = target;
|
||||
_parentElem = target.parent;
|
||||
}
|
||||
|
||||
protected override void RegisterCallbacksOnTarget()
|
||||
{
|
||||
target.RegisterCallback<DragEnterEvent>(OnDragEnter);
|
||||
target.RegisterCallback<DragLeaveEvent>(OnDragLeave);
|
||||
target.RegisterCallback<DragPerformEvent>(OnDragPerform);
|
||||
target.RegisterCallback<DragUpdatedEvent>(OnDragUpdate);
|
||||
}
|
||||
|
||||
protected override void UnregisterCallbacksFromTarget()
|
||||
{
|
||||
target.UnregisterCallback<DragEnterEvent>(OnDragEnter);
|
||||
target.UnregisterCallback<DragLeaveEvent>(OnDragLeave);
|
||||
target.UnregisterCallback<DragPerformEvent>(OnDragPerform);
|
||||
target.RegisterCallback<DragUpdatedEvent>(OnDragUpdate);
|
||||
}
|
||||
|
||||
|
||||
private void OnDragEnter(DragEnterEvent evt)
|
||||
{
|
||||
if (TargetComponent == null) return;
|
||||
|
||||
_avatarRoot = RuntimeUtil.FindAvatarTransformInParents(TargetComponent.transform);
|
||||
if (_avatarRoot == null) return;
|
||||
|
||||
_nowDragging = DragAndDrop.objectReferences.OfType<GameObject>()
|
||||
.Where(o => RuntimeUtil.FindAvatarTransformInParents(o.transform) == _avatarRoot)
|
||||
.ToArray();
|
||||
|
||||
if (_nowDragging.Length > 0)
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
||||
|
||||
_parentElem.AddToClassList("drop-area--drag-active");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDragUpdate(DragUpdatedEvent _)
|
||||
{
|
||||
if (_nowDragging.Length > 0) DragAndDrop.visualMode = DragAndDropVisualMode.Link;
|
||||
}
|
||||
|
||||
private void OnDragLeave(DragLeaveEvent evt)
|
||||
{
|
||||
_nowDragging = Array.Empty<GameObject>();
|
||||
_parentElem.RemoveFromClassList("drop-area--drag-active");
|
||||
}
|
||||
|
||||
private void OnDragPerform(DragPerformEvent evt)
|
||||
{
|
||||
if (_nowDragging.Length > 0 && TargetComponent != null && _avatarRoot != null)
|
||||
{
|
||||
var knownObjs = TargetComponent.Objects.Select(o => o.Object.Get(TargetComponent)).ToHashSet();
|
||||
|
||||
Undo.RecordObject(TargetComponent, "Add Toggled Objects");
|
||||
foreach (var obj in _nowDragging)
|
||||
{
|
||||
if (knownObjs.Contains(obj)) continue;
|
||||
|
||||
var aor = new AvatarObjectReference();
|
||||
aor.Set(obj);
|
||||
|
||||
var toggledObject = new ToggledObject { Object = aor, Active = !obj.activeSelf };
|
||||
TargetComponent.Objects.Add(toggledObject);
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(TargetComponent);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(TargetComponent);
|
||||
}
|
||||
|
||||
_nowDragging = Array.Empty<GameObject>();
|
||||
_parentElem.RemoveFromClassList("drop-area--drag-active");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
VisualElement {
|
||||
}
|
||||
|
||||
#group-box {
|
||||
#group-box {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
padding: 4px;
|
||||
@ -14,101 +11,42 @@
|
||||
/* background-color: rgba(0, 0, 0, 0.1); */
|
||||
}
|
||||
|
||||
#ListViewContainer {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#group-box > Label {
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
.group-root {
|
||||
#ListViewContainer {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.group-root Toggle {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.group-children {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.left-toggle {
|
||||
display: flex;
|
||||
.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.toggled-object-editor {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
.toggled-object-editor #f-object {
|
||||
.horizontal > * {
|
||||
height: 18px;
|
||||
margin: 0 1px;
|
||||
}
|
||||
|
||||
.horizontal > Label {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.horizontal > PropertyField > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#f-active {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#f-object {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#f-active > Toggle {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: -12px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.toggled-object-editor PropertyField Label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#f-change-type {
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.f-value {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
#f-value-delete {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.change-type-delete #f-value {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.change-type-delete #f-value-delete {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Add shape window */
|
||||
|
||||
.add-shape-popup {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.vline {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
border-top-width: 4px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
border-top-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.add-shape-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.add-shape-row Button {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.add-shape-popup ScrollView Label.placeholder {
|
||||
-unity-text-align: middle-center;
|
||||
}
|
||||
|
||||
.add-shape-row Label {
|
||||
flex-grow: 1;
|
||||
-unity-text-align: middle-left;
|
||||
.drop-area--drag-active > ListView ScrollView {
|
||||
background-color: rgba(0, 255, 255, 0.1);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
|
||||
<ui:VisualElement class="toggled-object-editor">
|
||||
<ed:PropertyField binding-path="Active" label="" name="f-active"/>
|
||||
<ed:PropertyField binding-path="Object" label="" name="f-object" class="f-object"/>
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ed:PropertyField name="f-active" binding-path="Active" label=""/>
|
||||
<ed:PropertyField name="f-object" binding-path="Object" label=""/>
|
||||
</ui:VisualElement>
|
||||
</UXML>
|
||||
</UXML>
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System.Globalization;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
@ -12,97 +10,107 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
}
|
||||
|
||||
private const string V_None = " ";
|
||||
private const string V_True = "ON";
|
||||
private const string V_False = "OFF";
|
||||
|
||||
private readonly TextField _visibleField;
|
||||
|
||||
private readonly FloatField _defaultValueField;
|
||||
private readonly Toggle _hasExplicitDefaultValueField;
|
||||
private readonly TextField _numberField;
|
||||
private readonly DropdownField _boolField;
|
||||
private readonly Toggle _hasExplicitDefaultSetField;
|
||||
|
||||
private ParameterSyncType _syncType;
|
||||
|
||||
public DefaultValueField()
|
||||
{
|
||||
// Hidden binding elements
|
||||
_defaultValueField = new FloatField();
|
||||
_hasExplicitDefaultSetField = new Toggle();
|
||||
_boolField = new DropdownField();
|
||||
_defaultValueField.style.display = DisplayStyle.None;
|
||||
_defaultValueField.bindingPath = nameof(ParameterConfig.defaultValue);
|
||||
_defaultValueField.RegisterValueChangedCallback(evt => UpdateVisibleField(evt.newValue, _hasExplicitDefaultValueField.value));
|
||||
_hasExplicitDefaultValueField = new Toggle();
|
||||
_hasExplicitDefaultValueField.style.display = DisplayStyle.None;
|
||||
_hasExplicitDefaultValueField.bindingPath = nameof(ParameterConfig.hasExplicitDefaultValue);
|
||||
_hasExplicitDefaultValueField.RegisterValueChangedCallback(evt => UpdateVisibleField(_defaultValueField.value, evt.newValue));
|
||||
|
||||
_boolField.choices.Add("");
|
||||
// Visible elements for input
|
||||
_numberField = new TextField();
|
||||
_numberField.isDelayed = true;
|
||||
_numberField.RegisterValueChangedCallback(evt => OnUpdateNumberValue(evt.newValue));
|
||||
_boolField = new DropdownField();
|
||||
_boolField.choices.Add(V_None);
|
||||
_boolField.choices.Add(V_True);
|
||||
_boolField.choices.Add(V_False);
|
||||
_boolField.RegisterValueChangedCallback(evt => OnUpdateBoolValue(evt.newValue));
|
||||
|
||||
_defaultValueField.RegisterValueChangedCallback(
|
||||
evt => UpdateVisibleField(evt.newValue, _hasExplicitDefaultSetField.value));
|
||||
_defaultValueField.bindingPath = nameof(ParameterConfig.defaultValue);
|
||||
|
||||
_hasExplicitDefaultSetField.RegisterValueChangedCallback(
|
||||
evt => UpdateVisibleField(_defaultValueField.value, evt.newValue));
|
||||
_hasExplicitDefaultSetField.bindingPath = nameof(ParameterConfig.hasExplicitDefaultValue);
|
||||
|
||||
_visibleField = new TextField();
|
||||
_visibleField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(evt.newValue))
|
||||
{
|
||||
_hasExplicitDefaultSetField.value = false;
|
||||
_defaultValueField.value = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasExplicitDefaultSetField.value = true;
|
||||
_defaultValueField.value = float.Parse(evt.newValue, CultureInfo.InvariantCulture);
|
||||
}
|
||||
});
|
||||
|
||||
_defaultValueField.style.width = 0;
|
||||
_defaultValueField.SetEnabled(false);
|
||||
_hasExplicitDefaultSetField.style.width = 0;
|
||||
_hasExplicitDefaultSetField.SetEnabled(false);
|
||||
|
||||
_boolField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (evt.newValue == V_True)
|
||||
_defaultValueField.value = 1;
|
||||
else
|
||||
_defaultValueField.value = 0;
|
||||
|
||||
_hasExplicitDefaultSetField.value = evt.newValue != "";
|
||||
});
|
||||
|
||||
|
||||
style.flexDirection = FlexDirection.Row;
|
||||
|
||||
Add(_visibleField);
|
||||
Add(_boolField);
|
||||
Add(_defaultValueField);
|
||||
Add(_hasExplicitDefaultSetField);
|
||||
Add(_hasExplicitDefaultValueField);
|
||||
Add(_numberField);
|
||||
Add(_boolField);
|
||||
}
|
||||
|
||||
public void ManualBindProperty(SerializedProperty property)
|
||||
public void OnUpdateSyncType(ParameterSyncType syncType)
|
||||
{
|
||||
_defaultValueField.BindProperty(property);
|
||||
_hasExplicitDefaultSetField.BindProperty(property);
|
||||
_syncType = syncType;
|
||||
|
||||
if (syncType != ParameterSyncType.Bool)
|
||||
{
|
||||
_numberField.style.display = DisplayStyle.Flex;
|
||||
_boolField.style.display = DisplayStyle.None;
|
||||
OnUpdateNumberValue(_numberField.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_numberField.style.display = DisplayStyle.None;
|
||||
_boolField.style.display = DisplayStyle.Flex;
|
||||
OnUpdateBoolValue(_boolField.value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnUpdateNumberValue(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
_defaultValueField.value = 0;
|
||||
_hasExplicitDefaultValueField.value = false;
|
||||
}
|
||||
else if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed)
|
||||
&& !float.IsNaN(parsed)
|
||||
&& !float.IsInfinity(parsed))
|
||||
{
|
||||
_defaultValueField.value = _syncType switch
|
||||
{
|
||||
ParameterSyncType.Int => Mathf.FloorToInt(Mathf.Clamp(parsed, 0, 255)),
|
||||
ParameterSyncType.Float => Mathf.Clamp(parsed, -1, 1),
|
||||
ParameterSyncType.Bool => parsed != 0 ? 1 : 0,
|
||||
_ => parsed,
|
||||
};
|
||||
_hasExplicitDefaultValueField.value = true;
|
||||
}
|
||||
|
||||
UpdateVisibleField(_defaultValueField.value, _hasExplicitDefaultValueField.value);
|
||||
}
|
||||
|
||||
private void OnUpdateBoolValue(string value)
|
||||
{
|
||||
_defaultValueField.value = value == V_True ? 1 : 0;
|
||||
_hasExplicitDefaultValueField.value = value != V_None;
|
||||
|
||||
UpdateVisibleField(_defaultValueField.value, _hasExplicitDefaultValueField.value);
|
||||
}
|
||||
|
||||
private void UpdateVisibleField(float value, bool hasExplicitValue)
|
||||
{
|
||||
if (Mathf.Abs(value) > 0.0000001)
|
||||
if (hasExplicitValue || Mathf.Abs(value) > 0.0000001)
|
||||
{
|
||||
hasExplicitValue = true;
|
||||
_numberField.SetValueWithoutNotify(value.ToString(CultureInfo.InvariantCulture));
|
||||
_boolField.SetValueWithoutNotify(value != 0 ? V_True : V_False);
|
||||
}
|
||||
|
||||
var str = hasExplicitValue ? value.ToString(CultureInfo.InvariantCulture) : "";
|
||||
_visibleField.SetValueWithoutNotify(str);
|
||||
|
||||
string boolStr;
|
||||
if (!hasExplicitValue)
|
||||
boolStr = "";
|
||||
else if (value > 0.5)
|
||||
boolStr = V_True;
|
||||
else
|
||||
boolStr = V_False;
|
||||
|
||||
_boolField.SetValueWithoutNotify(boolStr);
|
||||
{
|
||||
_numberField.SetValueWithoutNotify(string.Empty);
|
||||
_boolField.SetValueWithoutNotify(V_None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,15 +20,14 @@ namespace nadena.dev.modular_avatar.core.editor.Parameters
|
||||
Localization.UI.Localize(root);
|
||||
root.styleSheets.Add(uss);
|
||||
|
||||
var proot = root.Q<VisualElement>("Root");
|
||||
var type_field = proot.Q<DropdownField>("f-type");
|
||||
|
||||
var f_sync_type = proot.Q<VisualElement>("f-sync-type");
|
||||
var f_type = root.Q<DropdownField>("f-type");
|
||||
var f_sync_type = root.Q<DropdownField>("f-sync-type");
|
||||
var f_is_prefix = root.Q<VisualElement>("f-is-prefix");
|
||||
SetupPairedDropdownField(
|
||||
proot,
|
||||
type_field,
|
||||
root,
|
||||
f_type,
|
||||
f_sync_type,
|
||||
proot.Q<VisualElement>("f-is-prefix"),
|
||||
f_is_prefix,
|
||||
("Bool", "False", "params.syncmode.Bool"),
|
||||
("Float", "False", "params.syncmode.Float"),
|
||||
("Int", "False", "params.syncmode.Int"),
|
||||
@ -36,54 +35,51 @@ namespace nadena.dev.modular_avatar.core.editor.Parameters
|
||||
(null, "True", "params.syncmode.PhysBonesPrefix")
|
||||
);
|
||||
|
||||
f_sync_type.Q<DropdownField>().RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
var is_anim_only = evt.newValue == "Not Synced";
|
||||
var f_default = root.Q<DefaultValueField>();
|
||||
f_default.OnUpdateSyncType((ParameterSyncType)f_sync_type.index);
|
||||
f_sync_type.RegisterValueChangedCallback(evt => f_default.OnUpdateSyncType((ParameterSyncType)f_sync_type.index));
|
||||
|
||||
if (is_anim_only)
|
||||
proot.AddToClassList("st-anim-only");
|
||||
else
|
||||
proot.RemoveFromClassList("st-anim-only");
|
||||
});
|
||||
|
||||
var f_synced = proot.Q<Toggle>("f-synced");
|
||||
var f_local_only = proot.Q<Toggle>("f-local-only");
|
||||
var f_synced = root.Q<Toggle>("f-synced");
|
||||
var f_local_only = root.Q<Toggle>("f-local-only");
|
||||
|
||||
// Invert f_local_only and f_synced
|
||||
f_local_only.RegisterValueChangedCallback(evt => { f_synced.SetValueWithoutNotify(!evt.newValue); });
|
||||
|
||||
f_synced.RegisterValueChangedCallback(evt => { f_local_only.value = !evt.newValue; });
|
||||
|
||||
var internalParamAccessor = proot.Q<Toggle>("f-internal-parameter");
|
||||
var internalParamAccessor = root.Q<Toggle>("f-internal-parameter");
|
||||
internalParamAccessor.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (evt.newValue)
|
||||
proot.AddToClassList("st-internal-parameter");
|
||||
root.AddToClassList("st-internal-parameter");
|
||||
else
|
||||
proot.RemoveFromClassList("st-internal-parameter");
|
||||
root.RemoveFromClassList("st-internal-parameter");
|
||||
});
|
||||
|
||||
var remapTo = proot.Q<TextField>("f-remap-to");
|
||||
var defaultParam = proot.Q<Label>("f-default-param");
|
||||
var name = proot.Q<TextField>("f-name");
|
||||
var remapToInner = remapTo.Q<TextElement>();
|
||||
root.Q<VisualElement>("remap-to-group-disabled").SetEnabled(false);
|
||||
|
||||
Action updateDefaultParam = () =>
|
||||
var name = root.Q<TextField>("f-name");
|
||||
var remapTo = root.Q<TextField>("f-remap-to");
|
||||
var remapToInner = remapTo.Q<TextElement>();
|
||||
var remapToPlaceholder = root.Q<Label>("f-remap-to-placeholder");
|
||||
remapToPlaceholder.pickingMode = PickingMode.Ignore;
|
||||
|
||||
Action updateRemapToPlaceholder = () =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(remapTo.value))
|
||||
defaultParam.text = name.value;
|
||||
remapToPlaceholder.text = name.value;
|
||||
else
|
||||
defaultParam.text = "";
|
||||
remapToPlaceholder.text = "";
|
||||
};
|
||||
|
||||
name.RegisterValueChangedCallback(evt => { updateDefaultParam(); });
|
||||
name.RegisterValueChangedCallback(evt => { updateRemapToPlaceholder(); });
|
||||
|
||||
remapTo.RegisterValueChangedCallback(evt => { updateDefaultParam(); });
|
||||
remapTo.RegisterValueChangedCallback(evt => { updateRemapToPlaceholder(); });
|
||||
|
||||
defaultParam.RemoveFromHierarchy();
|
||||
remapToInner.Add(defaultParam);
|
||||
remapToPlaceholder.RemoveFromHierarchy();
|
||||
remapToInner.Add(remapToPlaceholder);
|
||||
|
||||
updateDefaultParam();
|
||||
updateRemapToPlaceholder();
|
||||
|
||||
return root;
|
||||
}
|
||||
@ -158,9 +154,6 @@ namespace nadena.dev.modular_avatar.core.editor.Parameters
|
||||
var p_type = GetAccessor(v_type);
|
||||
var p_prefix = GetAccessor(v_pbPrefix);
|
||||
|
||||
v_type.style.display = DisplayStyle.None;
|
||||
v_pbPrefix.style.display = DisplayStyle.None;
|
||||
|
||||
for (var i = 0; i < choices.Length; i++) target.choices.Add("" + i);
|
||||
|
||||
target.formatListItemCallback = s_n =>
|
||||
|
@ -1,48 +1,38 @@
|
||||
<ui:UXML
|
||||
xmlns:ui="UnityEngine.UIElements"
|
||||
xmlns:ma="nadena.dev.modular_avatar.core.editor"
|
||||
editor-extension-mode="False"
|
||||
>
|
||||
<ui:VisualElement name="Root">
|
||||
<ui:VisualElement class="horizontal no-label">
|
||||
<ui:TextField binding-path="nameOrPrefix" label="merge_parameter.ui.name" name="f-name" class="ndmf-tr"/>
|
||||
<ui:DropdownField name="f-type"/>
|
||||
</ui:VisualElement>
|
||||
|
||||
<ui:Toggle binding-path="isPrefix" name="f-is-prefix"/>
|
||||
<ui:DropdownField binding-path="syncType" name="f-sync-type"/>
|
||||
|
||||
<ui:VisualElement class="horizontal small-label">
|
||||
<ui:Toggle binding-path="internalParameter" name="f-internal-parameter"
|
||||
text="merge_parameter.ui.internalParameter" class="ndmf-tr no-left-margin"/>
|
||||
<ui:VisualElement class="v-separator hide-with-internal-param">
|
||||
<ui:VisualElement/>
|
||||
</ui:VisualElement>
|
||||
<ui:Label text="merge_parameter.ui.remapTo"
|
||||
class="ndmf-tr inner-label hide-with-internal-param no-left-margin"/>
|
||||
<ui:TextField name="f-remap-to" binding-path="remapTo" class="hide-with-internal-param"/>
|
||||
<ui:Label name="f-default-param" text="test test test"/>
|
||||
</ui:VisualElement>
|
||||
|
||||
<ui:VisualElement class="horizontal small-label st-pb-prefix__hide ">
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ui:Label text="merge_parameter.ui.defaultValue" class="ndmf-tr no-left-margin"/>
|
||||
<ma:DefaultValueField/>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="horizontal st-anim-only__hide">
|
||||
<ui:VisualElement class="v-separator">
|
||||
<ui:VisualElement/>
|
||||
</ui:VisualElement>
|
||||
<ui:Toggle binding-path="saved" text="merge_parameter.ui.saved"
|
||||
class="ndmf-tr st-pb-prefix__first-retained"/>
|
||||
<ui:Toggle binding-path="localOnly" text="merge_parameter.ui.localOnly" class="ndmf-tr"
|
||||
name="f-local-only"/>
|
||||
<ui:Toggle text="merge_parameter.ui.synced" class="ndmf-tr" name="f-synced"/>
|
||||
<ui:Toggle binding-path="m_overrideAnimatorDefaults" text="merge_parameter.ui.overrideAnimatorDefaults"
|
||||
class="ndmf-tr"/>
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements"
|
||||
xmlns:ma="nadena.dev.modular_avatar.core.editor">
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ui:TextField name="f-name" binding-path="nameOrPrefix" label=""/>
|
||||
<ui:DropdownField name="f-type"/>
|
||||
<ui:DropdownField name="f-sync-type" binding-path="syncType"/>
|
||||
<ui:Toggle name="f-is-prefix" binding-path="isPrefix"/>
|
||||
</ui:VisualElement>
|
||||
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ui:VisualElement name="remap-to-group-disabled" class="horizontal">
|
||||
<ui:Label text="merge_parameter.ui.remapTo" class="ndmf-tr"/>
|
||||
<ui:TextField name="f-remap-to-disabled"/>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="remap-to-group" class="horizontal">
|
||||
<ui:Label text="merge_parameter.ui.remapTo" class="ndmf-tr"/>
|
||||
<ui:TextField name="f-remap-to" binding-path="remapTo"/>
|
||||
<ui:Label name="f-remap-to-placeholder"/>
|
||||
</ui:VisualElement>
|
||||
<ui:Toggle name="f-internal-parameter" binding-path="internalParameter"
|
||||
text="merge_parameter.ui.internalParameter" class="ndmf-tr"/>
|
||||
</ui:VisualElement>
|
||||
|
||||
<ui:VisualElement class="horizontal st-pb-prefix__hide">
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ui:Label text="merge_parameter.ui.defaultValue" class="ndmf-tr"/>
|
||||
<ma:DefaultValueField/>
|
||||
</ui:VisualElement>
|
||||
<ui:Toggle binding-path="saved"
|
||||
text="merge_parameter.ui.saved" class="ndmf-tr st-anim-only__hide"/>
|
||||
<ui:Toggle name="f-local-only" binding-path="localOnly"
|
||||
text="merge_parameter.ui.localOnly" class="ndmf-tr st-anim-only__hide"/>
|
||||
<ui:Toggle name="f-synced"
|
||||
text="merge_parameter.ui.synced" class="ndmf-tr st-anim-only__hide"/>
|
||||
<ui:Toggle binding-path="m_overrideAnimatorDefaults"
|
||||
text="merge_parameter.ui.overrideAnimatorDefaults" class="ndmf-tr st-anim-only__hide"/>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
|
@ -1,120 +1,40 @@
|
||||
VisualElement {}
|
||||
|
||||
|
||||
/* I hate CSS precedence rules... */
|
||||
.horizontal .no-left-margin {
|
||||
margin-left: 0 !important;
|
||||
#ListViewContainer {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.horizontal .no-left-margin.unity-label {
|
||||
margin-left: 0 !important;
|
||||
.horizontal {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
.horizontal .no-left-margin Label.unity-label {
|
||||
margin-left: 0 !important;
|
||||
.horizontal > * {
|
||||
height: 18px;
|
||||
margin: 0 1px;
|
||||
}
|
||||
|
||||
.horizontal > Label {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.horizontal > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
.horizontal > PropertyField > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.v-separator {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.v-separator VisualElement {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.horizontal TextField {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.horizontal TextField Label.unity-label {
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
|
||||
.horizontal Label {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.horizontal > * {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.no-label Label.unity-base-field__label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#Root .horizontal #f-rename-destination {
|
||||
#f-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.inner-label > Label {
|
||||
margin-left: 6px;
|
||||
#f-sync-type {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.small-label Label.unity-label {
|
||||
min-width: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
VisualElement.small-label > * {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
VisualElement.small-label > PropertyField {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#Root #f-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#Root DefaultValueField {
|
||||
width: 60px;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.st-internal-parameter .hide-with-internal-param {
|
||||
.st-ty-Not-Synced .st-anim-only__hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
DefaultValueField DropdownField {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.st-ty-Bool DefaultValueField DropdownField {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.st-ty-Bool DefaultValueField TextField {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.st-ty-NotSynced DefaultValueField {
|
||||
#f-is-prefix {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -122,44 +42,55 @@ DefaultValueField DropdownField {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.st-anim-only .st-anim-only__hide {
|
||||
#f-remap-to, #f-remap-to-disabled {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#f-remap-to-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
#f-internal-parameter {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
#remap-to-group {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#remap-to-group-disabled {
|
||||
display: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.st-internal-parameter #remap-to-group {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.st-anim-only .st-pb-prefix__first-retained {
|
||||
margin-left: 0;
|
||||
.st-internal-parameter #remap-to-group-disabled {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.st-anim-only .st-pb-prefix__first-retained Label.unity-label {
|
||||
margin-left: 0;
|
||||
.horizontal > .horizontal {
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#f-remap-to {
|
||||
flex-grow: 1;
|
||||
DefaultValueField > * {
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#f-local-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
DefaultValueField TextInput {
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
/** Ghostly text for the renameTo text box **/
|
||||
Label#f-default-param {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 0 0 0;
|
||||
overflow: hidden;
|
||||
color: rgba(255, 255, 255, 0.4) !important;
|
||||
}
|
||||
|
||||
.DetectedParameter {
|
||||
flex-direction: row;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.DetectedParameter > Label {
|
||||
@ -168,10 +99,7 @@ Label#f-default-param {
|
||||
}
|
||||
|
||||
.SourceButton {
|
||||
flex-grow: 0;
|
||||
align-self: flex-end;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
.Select(x => mesh.GetBlendShapeName(x))
|
||||
.ToList();
|
||||
}
|
||||
catch (MissingComponentException e)
|
||||
catch (MissingComponentException)
|
||||
{
|
||||
shapeNames = null;
|
||||
}
|
||||
@ -72,7 +72,23 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
f_shape_name.SetEnabled(shapeNames != null);
|
||||
f_shape_name.choices = shapeNames ?? new();
|
||||
|
||||
f_shape_name.formatListItemCallback = name => shapeNames != null ? name : "<Missing SkinnedMeshRenderer>";
|
||||
f_shape_name.formatListItemCallback = name =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) return "";
|
||||
|
||||
if (shapeNames == null)
|
||||
{
|
||||
return $"<Missing SkinnedMeshRenderer>";
|
||||
}
|
||||
else if (!shapeNames.Contains(name))
|
||||
{
|
||||
return $"<color=\"red\">{name}</color>";
|
||||
}
|
||||
else
|
||||
{
|
||||
return name;
|
||||
}
|
||||
};
|
||||
f_shape_name.formatSelectedValueCallback = f_shape_name.formatListItemCallback;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements">
|
||||
<ui:VisualElement class="changed-shape-editor">
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ed:PropertyField binding-path="Object" label="" name="f-object" class="f-object"/>
|
||||
<ed:PropertyField binding-path="ChangeType" label="" name="f-change-type"/>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ui:DropdownField name="f-shape-name" binding-path="ShapeName"/>
|
||||
<ed:PropertyField binding-path="Value" label="" name="f-value" class="f-value"/>
|
||||
<ui:VisualElement name="f-value-delete" class="f-value"/>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ed:PropertyField name="f-object" binding-path="Object" label=""/>
|
||||
<ed:PropertyField name="f-change-type" binding-path="ChangeType" label=""/>
|
||||
</ui:VisualElement>
|
||||
</UXML>
|
||||
|
||||
<ui:VisualElement class="horizontal">
|
||||
<ui:DropdownField name="f-shape-name" binding-path="ShapeName"/>
|
||||
<ed:PropertyField name="f-value" binding-path="Value" label=""/>
|
||||
<ui:VisualElement name="f-value-delete"/>
|
||||
</ui:VisualElement>
|
||||
</UXML>
|
||||
|
@ -23,7 +23,7 @@ namespace nadena.dev.modular_avatar.core.editor.ShapeChanger
|
||||
|
||||
protected override void OnInnerInspectorGUI()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
EditorGUILayout.HelpBox("Unable to show override changes", MessageType.Info);
|
||||
}
|
||||
|
||||
protected override VisualElement CreateInnerInspectorGUI()
|
||||
|
@ -1,7 +1,4 @@
|
||||
VisualElement {
|
||||
}
|
||||
|
||||
#group-box {
|
||||
#group-box {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
padding: 4px;
|
||||
@ -14,65 +11,54 @@
|
||||
/* background-color: rgba(0, 0, 0, 0.1); */
|
||||
}
|
||||
|
||||
#ListViewContainer {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#group-box > Label {
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
.group-root {
|
||||
#ListViewContainer {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.group-root Toggle {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.group-children {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.left-toggle {
|
||||
display: flex;
|
||||
.horizontal {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
.changed-shape-editor .horizontal {
|
||||
flex-direction: row;
|
||||
.horizontal > * {
|
||||
height: 18px;
|
||||
margin: 0 1px;
|
||||
}
|
||||
|
||||
.changed-shape-editor #f-object {
|
||||
.horizontal > Label {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.horizontal > PropertyField > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#f-object {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.changed-shape-editor #f-shape-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.changed-shape-editor #f-change-type {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.changed-shape-editor #f-value {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.changed-shape-editor PropertyField Label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#f-change-type {
|
||||
width: 75px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.f-value {
|
||||
width: 75px;
|
||||
#f-shape-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#f-value {
|
||||
display: flex;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
#f-value-delete {
|
||||
display: none;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.change-type-delete #f-value {
|
||||
@ -81,37 +67,4 @@
|
||||
|
||||
.change-type-delete #f-value-delete {
|
||||
display: flex;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* Add shape window */
|
||||
|
||||
.add-shape-popup {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.vline {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
border-top-width: 4px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
border-top-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.add-shape-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.add-shape-row Button {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.add-shape-popup ScrollView Label.placeholder {
|
||||
-unity-text-align: middle-center;
|
||||
}
|
||||
|
||||
.add-shape-row Label {
|
||||
flex-grow: 1;
|
||||
-unity-text-align: middle-left;
|
||||
}
|
||||
|
@ -99,7 +99,7 @@
|
||||
"worldfixed.quest": "This component is not compatible with Android builds and will have no effect.",
|
||||
"worldfixed.normal": "This object will be fixed to world unless you fixed to avatar with constraint.",
|
||||
"fpvisible.normal": "This object will be visible in your first person view.",
|
||||
"fpvisible.NotUnderHead": "This component has no effect when not placed under the head bone.",
|
||||
"fpvisible.NotUnderHead": "This component has no effect when not placed under the head bone.\nIf this will be placed under the head bone through Bone Proxy etc., this warning can be ignored.",
|
||||
"fpvisible.quest": "This component is not compatible with Android builds and will have no effect.",
|
||||
"fpvisible.InPhysBoneChain": "This object is controlled by a Physics Bone chain and cannot be made visible in first person safely. Select the start of the chain instead.",
|
||||
"blendshape.mesh": "Mesh",
|
||||
@ -187,6 +187,8 @@
|
||||
"menuitem.prop.type.tooltip": "The type of this item",
|
||||
"menuitem.prop.value": "Value",
|
||||
"menuitem.prop.value.tooltip": "The value to set the parameter to when this control is used",
|
||||
"menuitem.prop.automatic_value": "Auto",
|
||||
"menuitem.prop.automatic_value.tooltip": "Automatically set this control to a unique value",
|
||||
"menuitem.prop.parameter": "Parameter",
|
||||
"menuitem.prop.label": "Label",
|
||||
"menuitem.prop.submenu_asset": "Submenu Asset",
|
||||
@ -238,7 +240,7 @@
|
||||
"setup_outfit.err.no_avatar_descriptor": "No avatar descriptor found in {0}'s parents. Make sure your outfit is placed inside your avatar.",
|
||||
"setup_outfit.err.no_animator": "Your avatar does not have an Animator component.",
|
||||
"setup_outfit.err.no_hips": "Your avatar does not have a Hips bone. Setup Outfit only works on humanoid avatars.",
|
||||
"setup_outfit.err.no_outfit_hips": "Unable to identify the Hips object for the outfit. Searched for objects containing the following names:",
|
||||
"setup_outfit.err.no_outfit_hips": "Unable to identify the Hips object for the outfit. Searched for objects containing the following names(case-insensitive):",
|
||||
"move_independently.group-header": "Objects to move together",
|
||||
"scale_adjuster.scale": "Scale adjustment",
|
||||
"scale_adjuster.adjust_children": "Adjust position of child objects",
|
||||
@ -279,4 +281,4 @@
|
||||
"ro_sim.effect_group.rule_inverted": "This rule is inverted",
|
||||
"ro_sim.effect_group.rule_inverted.tooltip": "This rule will be applied when one of its conditions is NOT met",
|
||||
"ro_sim.effect_group.conditions": "Conditions"
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
"menuinstall.showcontents": "メニュー内容を表示",
|
||||
"menuinstall.showcontents.notselected": "メニューが選択されていません",
|
||||
"menuinstall.devoptions": "プレハブ開発者向け設定",
|
||||
"menuinstall.menu_icon_too_large": "メニューに設定されているアイコンが256ピクセルより大きすぎます。",
|
||||
"menuinstall.menu_icon_too_large": "メニューに設定されているアイコンが256x256より大きいです。",
|
||||
"menuinstall.menu_icon_uncompressed": "メニューに設定されているアイコンが圧縮設定されていません。",
|
||||
"menuinstall.srcmenu": "インストールされるメニュー",
|
||||
"params.syncmode.NotSynced": "Animatorのみ",
|
||||
@ -31,32 +31,32 @@
|
||||
"merge_parameter.ui.name": "パラメーター名",
|
||||
"merge_parameter.ui.prefix": "PhysBone 接頭辞",
|
||||
"merge_parameter.ui.remapTo": "名前を変更",
|
||||
"merge_parameter.ui.remapTo.tooltip": "ここに新しい名前を入れることで、このパラメーターの名前を変更できます。これで名前かぶりを回避したり、あるいはあえて複数のギミックを連動できます。",
|
||||
"merge_parameter.ui.remapTo.tooltip": "ここに新しい名前を入れることで、このパラメーターの名前を変更できます。これで名前かぶりを回避したり、複数のギミックを連動させたりすることができます。",
|
||||
"merge_parameter.ui.remapTo.automatic": "(自動的に設定)",
|
||||
"merge_parameter.ui.defaultValue": "初期値",
|
||||
"merge_parameter.ui.defaultValue.tooltip": "アバターがリセット、または最初に着た時にこの値が採用されます。",
|
||||
"merge_parameter.ui.defaultValue.tooltip": "アバターをリセットした時、または最初に着た時にこの値が採用されます。",
|
||||
"merge_parameter.ui.saved": "保存する",
|
||||
"merge_parameter.ui.saved.tooltip": "保存されたパラメーターは、アバター変更やワールド移動で保持されます",
|
||||
"merge_parameter.ui.saved.tooltip": "保存されたパラメーターは、アバター変更やワールド移動をしても保持されます",
|
||||
"merge_parameter.ui.internalParameter": "自動リネーム",
|
||||
"merge_parameter.ui.internalParameter.tooltip": "有効にすると、名前かぶりを回避するために自動的に名前を変更します",
|
||||
"merge_parameter.ui.isPrefix": "PhysBone 接頭辞",
|
||||
"merge_parameter.ui.syncType": "パラメーター型",
|
||||
"merge_parameter.ui.synced": "同期する",
|
||||
"merge_parameter.ui.synced.tooltip": "有効にすると、ネットワーク上同期されます",
|
||||
"merge_parameter.ui.synced.tooltip": "有効にすると、このパラメーターは同期されます",
|
||||
"merge_parameter.ui.unregistered_foldout": "未登録パラメーター",
|
||||
"merge_parameter.ui.add_button": "追加",
|
||||
"merge_parameter.ui.details": "パラメーターの詳細設定",
|
||||
"merge_parameter.ui.overrideAnimatorDefaults": "アニメーターの初期値を設定",
|
||||
"merge_parameter.ui.overrideAnimatorDefaults": "アニメーターでの初期値を設定",
|
||||
"merge_armature.merge_target": "統合先",
|
||||
"merge_armature.merge_target.tooltip": "このオブジェクトを統合先のアーマチュアに統合します",
|
||||
"merge_armature.prefix": "接頭辞",
|
||||
"merge_armature.prefix.tooltip": "マージするボーンに付いている接頭辞",
|
||||
"merge_armature.prefix.tooltip": "統合されるボーンに付いている接頭辞",
|
||||
"merge_armature.suffix": "接尾辞",
|
||||
"merge_armature.suffix.tooltip": "マージするボーンに付いている接尾辞",
|
||||
"merge_armature.suffix.tooltip": "統合されるボーンに付いている接尾辞",
|
||||
"merge_armature.locked": "位置を固定",
|
||||
"merge_armature.locked.tooltip": "このオブジェクトのボーンを統合先のボーンに常に相互的に位置を合わせる。アニメーション制作向け",
|
||||
"merge_armature.locked.tooltip": "このオブジェクトのボーンと統合先のボーンの位置を常に合わせます。アニメーション作成時に便利です。",
|
||||
"merge_armature.adjust_names": "ボーン名を統合先に合わせる",
|
||||
"merge_armature.adjust_names.tooltip": "統合先のボーン名に合わせて、衣装のボーン名を合わせて変更します。統合先アバターに非対応の衣装導入向け機能です。",
|
||||
"merge_armature.adjust_names.tooltip": "衣装のボーン名をアバターのボーン名に合わせて変更します。アバターに非対応の衣装を導入する時に便利です。",
|
||||
"merge_armature.mangle_names": "名前かぶりを回避",
|
||||
"merge_armature.mangle_names.tooltip": "ほかのアセットとの名前かぶりを裂けるため、新規ボーンの名前を自動で変更する",
|
||||
"path_mode.Relative": "相対的(このオブジェクトからのパスを使用)",
|
||||
@ -66,12 +66,12 @@
|
||||
"merge_animator.delete_attached_animator": "付属アニメーターを削除",
|
||||
"merge_animator.delete_attached_animator.tooltip": "統合後、このオブジェクトについているアニメーターを削除します",
|
||||
"merge_animator.path_mode": "パスモード",
|
||||
"merge_animator.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトについているアニメーターでアニメーション編集できます",
|
||||
"merge_animator.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトにつけたアニメーターでアニメーションを編集することができます。",
|
||||
"merge_animator.match_avatar_write_defaults": "アバターのWrite Defaults設定に合わせる",
|
||||
"merge_animator.match_avatar_write_defaults.tooltip": "アバターの該当アニメーターのWrite Defaults設定に合わせます。アバター側の設定が矛盾する場合は、統合されるアニメーターのWD値がそのまま採用されます。",
|
||||
"merge_animator.relative_path_root": "相対的パスのルート",
|
||||
"merge_animator.relative_path_root.tooltip": "相対的パスはこのオブジェクトを基準に解釈されます。指定がない場合は、このコンポーネントがついているオブジェクトを基準とします。",
|
||||
"merge_animator.layer_priority": "レイヤー優先度",
|
||||
"merge_animator.layer_priority": "レイヤー統合優先度",
|
||||
"merge_animator.layer_priority.tooltip": "アニメーターにレイヤーが統合される順番を制御します。低い値から高い値の順に統合されます。マイナスの場合は元々のAvatar Descriptorについているコントローラーより前に統合され、ゼロ以上の場合はそのあとに統合されます。",
|
||||
"merge_armature.lockmode": "位置追従モード",
|
||||
"merge_armature.lockmode.not_locked.title": "追従なし",
|
||||
@ -81,28 +81,28 @@
|
||||
"merge_armature.lockmode.bidirectional.title": "アバター <=====> オブジェクト (双方向)",
|
||||
"merge_armature.lockmode.bidirectional.body": "アバターと統合されるアーマチュアは常に同じ位置になります。元のアバターを操作するアニメーションを作る時に便利かもしれません。有効にするためには、統合されるアーマチュアの位置を統合先と同じにしておく必要があります。",
|
||||
"merge_armature.reset_pos": "位置を元アバターに合わせてリセット",
|
||||
"merge_armature.reset_pos.info": "このコマンドは、衣装のボーンの位置をアバターのボーンの位置に合わせます。非対応衣装を導入するとき、アバウトに合わせるために便利です。",
|
||||
"merge_armature.reset_pos.info": "衣装のボーンの位置をアバターのボーンの位置に合わせます。非対応衣装を導入する際、アバウトに位置を合わせるのに便利です。",
|
||||
"merge_armature.reset_pos.adjust_rotation": "回転も合わせる",
|
||||
"merge_armature.reset_pos.adjust_scale": "スケールも合わせる",
|
||||
"merge_armature.reset_pos.execute": "実行",
|
||||
"merge_armature.reset_pos.heuristic_scale": "衣装の全体的なスケールをアバターに合わせる",
|
||||
"merge_armature.reset_pos.heuristic_scale.tooltip": "腕の長さを参考に、衣装全体のスケールをアバターに合わせます。非対応衣装を導入するときは推奨です。",
|
||||
"merge_armature.reset_pos.heuristic_scale.tooltip": "腕の長さを参考に、衣装全体のスケールをアバターに合わせます。非対応衣装を導入する時にお勧めです。",
|
||||
"merge_blend_tree.blend_tree": "ブレンドツリー",
|
||||
"merge_blend_tree.path_mode": "パスモード",
|
||||
"merge_blend_tree.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトについているアニメーターでアニメーション編集できます",
|
||||
"merge_blend_tree.path_mode.tooltip": "アニメーション内のパスを解釈するモード。相対的にすると、このオブジェクトにつけたアニメーターでアニメーションを編集することができます。",
|
||||
"merge_blend_tree.relative_path_root": "相対的パスのルート",
|
||||
"merge_blend_tree.relative_path_root.tooltip": "相対的パスはこのオブジェクトを基準に解釈されます。指定がない場合は、このコンポーネントがついているオブジェクトを基準とします。",
|
||||
"worldfixed.quest": "このコンポーネントはアンドロイドビルド非対応のため無効となっています。",
|
||||
"worldfixed.quest": "このコンポーネントはアンドロイドビルドに非対応であるため、無効となっています。",
|
||||
"worldfixed.normal": "このオブジェクトはConstraint等でアバターに追従させない限りワールドに固定されます。",
|
||||
"fpvisible.normal": "このオブジェクトは一人視点で表示されます。",
|
||||
"fpvisible.NotUnderHead": "このコンポーネントはヘッドボーン外では効果がありません。",
|
||||
"fpvisible.quest": "このコンポーネントはアンドロイドビルド非対応のため無効となっています。",
|
||||
"fpvisible.InPhysBoneChain": "このオブジェクトはPhysBoneに制御されているため、一人視点で表示できません。PhysBoneの始点を指定してください。",
|
||||
"fpvisible.normal": "このオブジェクトは一人称視点で表示されます。",
|
||||
"fpvisible.NotUnderHead": "このコンポーネントは頭ボーンの配下でないと効果がありません。\n(Bone Proxyなどで頭ボーンの配下に配置される場合は効果あります)",
|
||||
"fpvisible.quest": "このコンポーネントはアンドロイドビルドに非対応であるため、無効となっています。",
|
||||
"fpvisible.InPhysBoneChain": "このオブジェクトはPhysBoneで制御されているため、一人称視点でうまく表示させることができません。PhysBoneの始点を指定してください。",
|
||||
"blendshape.mesh": "メッシュ",
|
||||
"blendshape.source": "元メッシュのブレンドシェープ",
|
||||
"blendshape.target": "このメッシュのブレンドシェープ",
|
||||
"hint.not_in_avatar": "このコンポーネントが正しく動作するには、アバター内に配置する必要があります。",
|
||||
"boneproxy.err.MovingTarget": "他のモジュラーアバターコンポーネントで移動されるオブジェクトを指定できません。",
|
||||
"blendshape.source": "元メッシュのブレンドシェイプ",
|
||||
"blendshape.target": "このメッシュのブレンドシェイプ",
|
||||
"hint.not_in_avatar": "このコンポーネントを正しく動作させるには、アバター内に配置する必要があります。",
|
||||
"boneproxy.err.MovingTarget": "他のモジュラーアバターコンポーネントで移動されるオブジェクトは指定できません。",
|
||||
"boneproxy.err.NotInAvatar": "アバター内のオブジェクトを指定してください。",
|
||||
"boneproxy.attachment": "配置モード",
|
||||
"boneproxy.attachment.AsChildAtRoot": "子として・ルートに配置",
|
||||
@ -111,28 +111,28 @@
|
||||
"boneproxy.attachment.AsChildKeepRotation": "子として・ワールド向きを維持",
|
||||
"mesh_settings.header_probe_anchor": "Anchor Override 設定",
|
||||
"mesh_settings.inherit_probe_anchor": "設定モード",
|
||||
"mesh_settings.probe_anchor": "アンカーオーバーライド",
|
||||
"mesh_settings.probe_anchor.tooltip": "このオブジェクトとその子のレンダラーのAnchorOverrideを設定します。",
|
||||
"mesh_settings.probe_anchor": "Anchor Override",
|
||||
"mesh_settings.probe_anchor.tooltip": "このオブジェクトとその子のレンダラーのAnchor Overrideを設定します。",
|
||||
"mesh_settings.header_bounds": "Bounds 設定",
|
||||
"mesh_settings.inherit_bounds": "設定モード",
|
||||
"mesh_settings.root_bone": "ルートボーン",
|
||||
"mesh_settings.root_bone.tooltip": "このオブジェクトとその子のメッシュで設定されるルートボーン。メッシュのバウンズを計算するための参照点として使用されます。",
|
||||
"mesh_settings.bounds": "バウンズ",
|
||||
"mesh_settings.root_bone.tooltip": "このオブジェクトとその子のメッシュで設定されるルートボーン。メッシュのBoundsを計算する際の基準として使用されます。",
|
||||
"mesh_settings.bounds": "Bounds",
|
||||
"mesh_settings.bounds.tooltip": "このオブジェクトとその子のメッシュで設定されるバウンズ。画面外のメッシュのレンダリングを省略するかどうかを決定するために使用されます。",
|
||||
"mesh_settings.inherit_mode.Inherit": "継承",
|
||||
"mesh_settings.inherit_mode.Set": "設定",
|
||||
"mesh_settings.inherit_mode.DontSet": "設定しない(メッシュ本体の設定のまま)",
|
||||
"mesh_settings.inherit_mode.SetOrInherit": "親が指定されてる時は継承、または設定",
|
||||
"mesh_settings.inherit_mode.SetOrInherit": "親で指定されている時は継承、それ以外では設定",
|
||||
"pb_blocker.help": "このオブジェクトは親のPhysBoneから影響を受けなくなります。",
|
||||
"hint.bad_vrcsdk": "使用中のVRCSDKのバージョンとは互換性がありません。\n\nVRCSDKを更新してみてください。それでもだめでしたら、Modular Avatarにも最新版が出てないかチェックしてください。",
|
||||
"hint.bad_vrcsdk": "使用中のVRCSDKのバージョンとは互換性がありません。\n\nVRCSDKを更新してみてください。それでも駄目な場合、Modular Avatarにも最新版が出ていないか確認してみてください。",
|
||||
"error.stack_trace": "スタックトレース(バグを報告する時は必ず添付してください!)",
|
||||
"error.merge_armature.circular_dependency": "[MA-0001] Merge Armatureに循環参照があります",
|
||||
"error.merge_armature.circular_dependency:description": "Merge Armature コンポーネントは、自分自身、または自分の子をマージターゲットとして参照しています。",
|
||||
"error.merge_armature.circular_dependency:hint": "Merge Armature は通常、ターゲットフィールドにアバター本体の Armature オブジェクトを指定する必要があります。衣装自体を指定しないでください。",
|
||||
"error.merge_armature.physbone_on_humanoid_bone": "[MA-0002] ヒューマノイドボーンにPhysBoneコンポーネントを検出",
|
||||
"error.merge_armature.physbone_on_humanoid_bone:hint": "一部のヒューマノイドボーンは、PhysBonesによって制御されています。 マージ対象の対応するヒューマノイドボーンとは位置が異なるため、適切にマージすることはできません。マージするにはヒューマノイドボーンのPhysBonesを取り除く必要があります。",
|
||||
"error.merge_armature.circular_dependency:description": "Merge Armatureコンポーネントの統合先として、自分自身、または自分の子が参照されています。",
|
||||
"error.merge_armature.circular_dependency:hint": "通常、Merge Armatureは統合先としてアバター本体のArmatureオブジェクトを指定する必要があります。衣装自体を指定しないように注意してください。",
|
||||
"error.merge_armature.physbone_on_humanoid_bone": "[MA-0002] HumanoidボーンにPhysBoneコンポーネントがついています。",
|
||||
"error.merge_armature.physbone_on_humanoid_bone:hint": "一部のHumanoidボーンがPhysBoneによって制御されています。対応する統合先のHumanoidボーンと位置が異なるため、適切に統合することができません。統合するにはHumanoidボーンについているPhysBoneを取り除く必要があります。",
|
||||
"error.merge_blend_tree.missing_tree": "[MA-0009] ブレンドツリーが指定されていません",
|
||||
"error.merge_blend_tree.missing_tree:hint": "Merge Blend Treeが動作するには、どのブレンドツリーを統合するかを指定する必要があります。「ブレンドツリー」欄を設定してみてください。",
|
||||
"error.merge_blend_tree.missing_tree:hint": "Merge Blend Treeが動作するには、どのブレンドツリーを統合するか指定する必要があります。「ブレンドツリー」欄を設定しているかご確認ください。",
|
||||
"error.internal_error": "[MA-9999] 内部エラーが発生しました:{0}\n以下のオブジェクトの処理中に発生しました:",
|
||||
"error.merge_animator.param_type_mismatch": "[MA-0003] パラメータの型が競合しています",
|
||||
"error.merge_animator.param_type_mismatch:description": "パラメータ {0} には複数の型が指定されています: {1} != {2}",
|
||||
@ -142,37 +142,37 @@
|
||||
"error.rename_params.type_conflict:description": "パラメータ {0} には複数の型が指定されています: {1} != {2}",
|
||||
"error.rename_params.default_value_conflict": "[MA-0007] 初期値の競合",
|
||||
"error.rename_params.default_value_conflict:description": "パラメータ {0} には複数の初期値が指定されています: {1} != {2}",
|
||||
"error.rename_params.default_value_conflict:hint": "予測不可能な動作を避けるために、MAパラメータコンポーネントのデフォルト値フィールドはパラメーター名毎に一個のコンポーネント以外空白のままにしてください。 複数の値が存在する場合、Modular Avatarは階層順に指定された最初のデフォルト値を選択します。",
|
||||
"error.replace_object.null_target": "[MA-0008] ターゲットが指定されていません",
|
||||
"error.replace_object.null_target:hint": "Replace Object には置き換えるべきターゲットオブジェクトが必要です。設定してみてください。",
|
||||
"validation.blendshape_sync.no_local_renderer": "[MA-1000] このオブジェクトにはSkinnedMeshRendererがありません。",
|
||||
"validation.blendshape_sync.no_local_renderer:hint": "Blendshape Syncは同じGameObject上のSkinned Mesh Rendererに作用します。正しいオブジェクトに追加しましたか?",
|
||||
"validation.blendshape_sync.no_local_mesh": "[MA-1001] このオブジェクトにはSkinnedMeshRendererがありますが、メッシュがありません。",
|
||||
"error.rename_params.default_value_conflict:hint": "予測不可能な動作を避けるため、MA Parametersコンポーネントの初期値フィールドはパラメーター名毎に1つだけしか指定しないようにし、他のコンポーネントでは空白のままにしてください。複数の値が存在する場合、Modular Avatarは階層順で最初に指定された初期値を採用します。",
|
||||
"error.replace_object.null_target": "[MA-0008] 置き換え先が指定されていません",
|
||||
"error.replace_object.null_target:hint": "Replace Objectは置き換え先のオブジェクトを指定する必要があります。",
|
||||
"validation.blendshape_sync.no_local_renderer": "[MA-1000] このオブジェクトにはSkinned Mesh Rendererがありません。",
|
||||
"validation.blendshape_sync.no_local_renderer:hint": "Blendshape Syncは同じGameObject上のSkinned Mesh Rendererに作用します。コンポーネントが正しいオブジェクトに追加されているか確認してください。",
|
||||
"validation.blendshape_sync.no_local_mesh": "[MA-1001] このオブジェクトにはSkinned Mesh Rendererがありますが、メッシュがありません。",
|
||||
"validation.blendshape_sync.no_local_mesh:hint": "このオブジェクトの Skinned Mesh Renderer の設定が壊れている可能性があります。 元のプレハブまたはFBXからオブジェクトを再作成してみてください。",
|
||||
"validation.blendshape_sync.no_bindings": "[MA-1002] このBlendshapeSyncにはバインドが設定されていません。",
|
||||
"validation.blendshape_sync.no_bindings": "[MA-1002] このBlendshape Syncにはバインドが設定されていません。",
|
||||
"validation.blendshape_sync.no_bindings:hint": "Blendshape Syncは、どのブレンドシェイプを同期するかを知る必要があります。追加するには、「+」ボタンをクリックしてください。",
|
||||
"validation.blendshape_sync.missing_local_shape": "[MA-1003] 同期先のメッシュに該当するブレンドシェープ「{0}」がありません。",
|
||||
"validation.blendshape_sync.missing_local_shape:description": "ローカルブレンドシェイプがありません: {0}",
|
||||
"validation.blendshape_sync.missing_local_shape:hint": "ターゲットオブジェクトから値を受け取るように設定されたブレンドシェイプがありません。赤で示されているブレンドシェイプ名を変更してみてください。",
|
||||
"validation.blendshape_sync.missing_target_shape": "[MA-1004] 同期先メッシュにブレンドシェープ「{0}」が見つかりません",
|
||||
"validation.blendshape_sync.missing_target_shape:description": "ターゲットブレンドシェイプがありません: {0}",
|
||||
"validation.blendshape_sync.missing_target_shape:hint": "ローカルオブジェクトに値を「送る」ように設定されたブレンドシェイプがありません。赤で示されているブレンドシェイプ名を変更してみてください。",
|
||||
"validation.blendshape_sync.no_target": "[MA-1005] このBlendshapeSyncには同期元が設定されていないバインドがあります。",
|
||||
"validation.blendshape_sync.no_target:hint": "どのオブジェクトからBlendshapeを同期するかを教える必要があります。メッシュを設定してみてください。",
|
||||
"validation.blendshape_sync.missing_target_renderer": "[MA-1006] 同期元のオブジェクトにはSkinnedMeshRendererがありません。",
|
||||
"validation.blendshape_sync.missing_target_renderer:hint": "Blendshape Syncは、対象オブジェクトのSkinned Mesh Rendererからブレンド形状の値を受け取ります。正しいオブジェクトに追加しましたか?",
|
||||
"validation.blendshape_sync.missing_target_mesh": "[MA-1007] 同期元のオブジェクトにはSkinnedMeshRendererがありますが、メッシュがありません。",
|
||||
"validation.blendshape_sync.missing_target_mesh:hint": "ターゲットオブジェクトの Skinned Mesh Renderer の設定が壊れている可能性があります。 元のプレハブまたはFBXからオブジェクトを再作成してみてください。",
|
||||
"validation.bone_proxy.no_target": "[MA-1100] ターゲットオブジェクトが未設定、もしくは存在しません。",
|
||||
"validation.bone_proxy.no_target:hint": "ボーンプロキシがどのオブジェクトに追尾するかを知る必要があります。追尾すべきオブジェクトをターゲットフィールドに設定してみてください。",
|
||||
"validation.blendshape_sync.missing_local_shape": "[MA-1003] 同期先のメッシュにブレンドシェイプ「{0}」がありません。",
|
||||
"validation.blendshape_sync.missing_local_shape:description": "ブレンドシェイプ「{0}」がありません。",
|
||||
"validation.blendshape_sync.missing_local_shape:hint": "値を「受け取る」ように設定されたブレンドシェイプが同期先のメッシュに存在しません。赤で示されているブレンドシェイプ名を変更してみてください。",
|
||||
"validation.blendshape_sync.missing_target_shape": "[MA-1004] 同期元のメッシュにブレンドシェイプ「{0}」がありません。",
|
||||
"validation.blendshape_sync.missing_target_shape:description": "ブレンドシェイプ「{0}」がありません。",
|
||||
"validation.blendshape_sync.missing_target_shape:hint": "値を「送る」ように設定されたブレンドシェイプが同期元のメッシュに存在しません。赤で示されているブレンドシェイプ名を変更してみてください。",
|
||||
"validation.blendshape_sync.no_target": "[MA-1005] このBlendshape Syncには同期元が設定されていないバインドがあります。",
|
||||
"validation.blendshape_sync.no_target:hint": "どのオブジェクトからBlendshapeを同期するか指定する必要があります。メッシュを設定してください。",
|
||||
"validation.blendshape_sync.missing_target_renderer": "[MA-1006] 同期元のオブジェクトにSkinned Mesh Rendererがありません。",
|
||||
"validation.blendshape_sync.missing_target_renderer:hint": "Blendshape Syncは、同期元のSkinned Mesh Rendererからブレンドシェイプの値を受け取ります。コンポーネントが正しいオブジェクトに追加されているか確認してください。",
|
||||
"validation.blendshape_sync.missing_target_mesh": "[MA-1007] 同期元のオブジェクトにはSkinned Mesh Rendererがありますが、メッシュがありません。",
|
||||
"validation.blendshape_sync.missing_target_mesh:hint": "同期元のオブジェクトの Skinned Mesh Renderer の設定が壊れている可能性があります。 元のプレハブまたはFBXからオブジェクトを再作成してみてください。",
|
||||
"validation.bone_proxy.no_target": "[MA-1100] ターゲットオブジェクトが未設定であるか、存在しません。",
|
||||
"validation.bone_proxy.no_target:hint": "Bone Proxyはどのオブジェクトに追従させるかを指定する必要があります。追従させたい対象のオブジェクトをターゲット欄に設定してみてください。",
|
||||
"validation.menu_installer.no_menu": "[MA-1200] インストールするメニューがありません。",
|
||||
"validation.menu_installer.no_menu:hint": "Menu Installer は、どのメニューをインストールするべきか設定する必要があります。 「Prefab開発者向けオプション」内の「インストールするメニュー」フィールドを設定するか、MA Menu Itemコンポーネントを追加してみてください。",
|
||||
"validation.merge_animator.no_animator": "[MA-1300] Animator Controllerが設定されていません",
|
||||
"validation.merge_animator.no_animator:hint": "Animatorをマージするには、どのアニメーターを統合するかを設定する必要があります。「統合されるアニメーター」を設定してみてください。",
|
||||
"validation.merge_armature.no_target": "[MA-1400] 統合先が未設定、もしくは存在しません。",
|
||||
"validation.merge_animator.no_animator": "[MA-1300] Animator Controllerが未設定であるか、存在しません。",
|
||||
"validation.merge_animator.no_animator:hint": "どのアニメーターを統合するか設定する必要があります。「統合されるアニメーター」を設定してみてください。",
|
||||
"validation.merge_armature.no_target": "[MA-1400] 統合先が未設定であるか、存在しません。",
|
||||
"validation.merge_armature.no_target:hint": "Merge Armatureはどこに統合するか設定する必要があります。「統合先」を設定してみてください。",
|
||||
"validation.merge_armature.target_is_child": "[MA-1500] 統合先をこのオブジェクトの子にすることができません",
|
||||
"validation.merge_armature.target_is_child:hint": "Merge Armature は自身の子に統合できません。「統合先」を別のオブジェクトに設定してみてください。",
|
||||
"validation.merge_armature.target_is_child": "[MA-1500] 統合先として自身の子を指定することはできません。",
|
||||
"validation.merge_armature.target_is_child:hint": "Merge Armatureで自身の子に統合することはできません。「統合先」を別のオブジェクトに設定してみてください。",
|
||||
"submenu_source.Children": "子オブジェクトから生成",
|
||||
"submenu_source.MenuAsset": "Expressions Menu アセットを指定",
|
||||
"menuitem.showcontents": "メニュー内容を表示",
|
||||
@ -182,25 +182,27 @@
|
||||
"menuitem.prop.type": "タイプ",
|
||||
"menuitem.prop.type.tooltip": "この項目の種別",
|
||||
"menuitem.prop.value": "パラメーター値",
|
||||
"menuitem.prop.value.tooltip": "この項目が操作されたとき、パラメーターが設定される値",
|
||||
"menuitem.prop.value.tooltip": "この項目が操作されたとき、パラメーターに設定される値",
|
||||
"menuitem.prop.automatic_value": "自動",
|
||||
"menuitem.prop.automatic_value.tooltip": "かぶらない値を自動的に割り振る",
|
||||
"menuitem.prop.parameter": "パラメーター名",
|
||||
"menuitem.prop.label": "表示名",
|
||||
"menuitem.prop.submenu_asset": "サブメニューアセット",
|
||||
"menuitem.prop.submenu_asset.tooltip": "サブメニューとして引用するアセット",
|
||||
"menuitem.prop.submenu_source": "サブメニュー引用元",
|
||||
"menuitem.prop.submenu_source.tooltip": "このサブメニューの内容をどこから引用するべきかを指定",
|
||||
"menuitem.prop.submenu_source.tooltip": "このサブメニューの内容をどこから引用するべきか指定します。",
|
||||
"menuitem.prop.source_override": "引用元オブジェクト",
|
||||
"menuitem.prop.source_override.tooltip": "指定した場合は、指定したオブジェクトの子をメニューの内容として指定します。指定されてない場合はこのオブジェクト直下の子を使用します。",
|
||||
"menuitem.prop.source_override.tooltip": "指定した場合、指定したオブジェクトの子をメニューの内容として指定します。指定されてない場合はこのオブジェクト直下の子を使用します。",
|
||||
"menuitem.prop.is_default": "初期設定にする",
|
||||
"menuitem.prop.is_default.tooltip": "ONの場合、アバター初期化の際にこのメニューアイテムを選択します",
|
||||
"menuitem.prop.is_default.tooltip": "有効になっていると、アバター初期化の際にこのメニューアイテムを選択した状態にします。",
|
||||
"menuitem.prop.is_saved": "保存する",
|
||||
"menuitem.prop.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動するときこの設定が保持されます。",
|
||||
"menuitem.prop.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動をしてもこの設定が保持されます。",
|
||||
"menuitem.prop.is_synced": "同期する",
|
||||
"menuitem.prop.is_synced.tooltip": "有効の場合はほかのプレイヤーに同期されます。",
|
||||
"menuitem.param.rotation": "回転パラメーター名",
|
||||
"menuitem.param.rotation.tooltip": "このメニューアイテムの回転に連動するべきパラメーター",
|
||||
"menuitem.prop.is_synced.tooltip": "有効になっていると、メニューがほかのプレイヤーに同期されます。",
|
||||
"menuitem.param.rotation": "ラジアルメニュー用パラメーター名",
|
||||
"menuitem.param.rotation.tooltip": "このラジアルメニューの操作と連動するべきパラメーター",
|
||||
"menuitem.param.horizontal": "横パラメーター名",
|
||||
"menuitem.param.horizontal.tooltip": "横操作に連動するパラメーター名",
|
||||
"menuitem.param.horizontal.tooltip": "左右操作に連動するパラメーター名",
|
||||
"menuitem.param.vertical": "縦パラメーター名",
|
||||
"menuitem.param.vertical.tooltip": "上下操作に連動するパラメーター名",
|
||||
"menuitem.label.control_labels_and_params": "表示名・パラメーター",
|
||||
@ -216,29 +218,29 @@
|
||||
"control_group.foldout.actions": "アクション",
|
||||
"control_group.foldout.menu_items": "関連付けされたメニューアイテム",
|
||||
"control_group.is_saved": "保存する",
|
||||
"control_group.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動するときこの設定が保持されます。",
|
||||
"control_group.is_saved.tooltip": "有効になっていると、アバター変更やワールド移動をしてもこの設定が保持されます。",
|
||||
"control_group.is_synced": "同期する",
|
||||
"control_group.is_synced.tooltip": "有効の場合はほかのプレイヤーに同期されます。",
|
||||
"control_group.is_synced.tooltip": "有効になっていると、メニューがほかのプレイヤーに同期されます。",
|
||||
"control_group.default_value": "初期値",
|
||||
"control_group.default_value.unset": "(どれも選択されない)",
|
||||
"animation_gen.duplicate_binding": "別々のコントロールグループから、同じパラメーターが操作されています。パラメーター:{0}",
|
||||
"animation_gen.multiple_defaults": "同じコントロールグループに初期設定に指定されたメニューアイテムが複数あります。",
|
||||
"animation_gen.multiple_defaults": "同じコントロールグループに初期設定にするとして指定されたメニューアイテムが複数あります。",
|
||||
"menuitem.misc.add_item": "メニューアイテムを追加",
|
||||
"replace_object.target_object": "上書き先",
|
||||
"setup_outfit.err.header.notarget": "Setup outfit の処理が失敗しました",
|
||||
"setup_outfit.err.header": "Setup outfit が「{0}」を処理中に失敗しました。",
|
||||
"replace_object.target_object": "置き換え先",
|
||||
"setup_outfit.err.header.notarget": "Setup Outfit の処理に失敗しました",
|
||||
"setup_outfit.err.header": "Setup Outfit が「{0}」を処理中に失敗しました。",
|
||||
"setup_outfit.err.unknown": "原因不明のエラーが発生しました。",
|
||||
"setup_outfit.err.no_selection": "オブジェクトが選択されていません。",
|
||||
"setup_outfit.err.run_on_avatar_itself": "Setup Outfitはアバター自体ではなく、衣装のほうで実行してください。\n\nキメラアバターを作る場合は、中のほうのAvatar Descriptorを消して、衣装として扱ってください。",
|
||||
"setup_outfit.err.multiple_avatar_descriptors": "「{0}」とその親に、複数のavatar descriptorを発見しました。\n\nキメラアバターを作る場合は、中のほうのAvatar Descriptorを消して、衣装として扱ってください。",
|
||||
"setup_outfit.err.no_avatar_descriptor": "「{0}」の親に、avatar descriptorが見つかりませんでした。衣装のオブジェクトをアバターの中に配置してください。",
|
||||
"setup_outfit.err.run_on_avatar_itself": "Setup Outfitはアバター自体ではなく、衣装の方で実行してください。\n\nキメラアバターを作る場合は、中の方のAvatar Descriptorを消して、衣装として扱ってください。",
|
||||
"setup_outfit.err.multiple_avatar_descriptors": "「{0}」とその親で複数のAvatar Descriptorを発見しました。\n\nキメラアバターを作る場合は、中の方のAvatar Descriptorを消して、衣装として扱ってください。",
|
||||
"setup_outfit.err.no_avatar_descriptor": "「{0}」の親にAvatar Descriptorが見つかりませんでした。衣装のオブジェクトをアバターの中に配置してください。",
|
||||
"setup_outfit.err.no_animator": "アバターにAnimatorコンポーネントがありません。",
|
||||
"setup_outfit.err.no_hips": "アバターにHipsボーンがありません。なお、Setup Outfitはヒューマノイドアバター以外には対応していません。",
|
||||
"setup_outfit.err.no_outfit_hips": "衣装のHipsボーンを発見できませんでした。以下の名前を含むボーンを探しました:",
|
||||
"move_independently.group-header": "一緒に動かすオブジェクト",
|
||||
"setup_outfit.err.no_hips": "アバターにHipsボーンがありません。なお、Setup OutfitはHumanoidアバター以外には対応していません。",
|
||||
"setup_outfit.err.no_outfit_hips": "衣装のHipsボーンを発見できませんでした。アクセサリー等に対してSetup Outfitを試みた場合は、代わりにBone Proxyコンポーネントを使って配置してみてください。\n以下の名前を含むボーンを(大文字・小文字を区別つけずに)探しました:",
|
||||
"move_independently.group-header": "同時に動かすオブジェクト",
|
||||
"scale_adjuster.scale": "Scale調整値",
|
||||
"scale_adjuster.adjust_children": "子オブジェクトの位置を調整",
|
||||
"world_fixed_object.err.unsupported_platform": "World Fixed Objectがこのプラットフォームに対応していません",
|
||||
"world_fixed_object.err.unsupported_platform": "World Fixed Objectはこのプラットフォームに対応していません。",
|
||||
"world_fixed_object.err.unsupported_platform:description": "World Fixed ObjectはAndroid向けビルドには対応していないため、動作しません。",
|
||||
"ma_info.param_usage_ui.header": "Expressions Parameter 使用状況",
|
||||
"ma_info.param_usage_ui.other_objects": "このアバター内の他のオブジェクト",
|
||||
@ -248,9 +250,8 @@
|
||||
"reactive_object.inverse": "条件を反転",
|
||||
"reactive_object.material-setter.set-to": "変更先のマテリアル ",
|
||||
"menuitem.misc.add_toggle": "トグルを追加",
|
||||
"ro_sim.open_debugger_button": "リアクションデバッグツールを開く",
|
||||
"ro_sim.window.title": "MA リアクションデバッグツール",
|
||||
|
||||
"ro_sim.open_debugger_button": "Reaction デバッガーを開く",
|
||||
"ro_sim.window.title": "MA Reaction デバッガー",
|
||||
"ro_sim.header.inspecting": "表示中のオブジェクト",
|
||||
"ro_sim.header.clear_overrides": "すべてのオーバーライドを解除",
|
||||
"ro_sim.header.object_state": "オブジェクトのアクティブ状態",
|
||||
@ -259,19 +260,17 @@
|
||||
"ro_sim.header.override_gameobject_state": "GameObject のアクティブ状態をオーバーライド",
|
||||
"ro_sim.header.override_menuitem_state": "MenuItem の選択状態をオーバーライト",
|
||||
"ro_sim.affected_by.title": "以下のルールに影響されています",
|
||||
|
||||
"ro_sim.effect_group.component": "Reactive Component",
|
||||
"ro_sim.effect_group.controls_obj_state": "オブジェクトのアクティブ状態を設定する➡",
|
||||
"ro_sim.effect_group.controls_obj_state": "オブジェクトのアクティブ状態を次に設定する➡",
|
||||
"ro_sim.effect_group.target_component": "コンポーネント",
|
||||
"ro_sim.effect_group.target_component.tooltip": "Reactive Componentに影響されるコンポーネント",
|
||||
"ro_sim.effect_group.property": "プロパティ",
|
||||
"ro_sim.effect_group.property.tooltip": "設定されるプロパティ",
|
||||
"ro_sim.effect_group.value": "値",
|
||||
"ro_sim.effect_group.value.tooltip": "上記 Reactive Component が活性状態の時に設定される値",
|
||||
"ro_sim.effect_group.value.tooltip": "上記の Reactive Component がアクティブな時に設定される値",
|
||||
"ro_sim.effect_group.material": "マテリアル",
|
||||
"ro_sim.effect_group.material.tooltip": "上記 Reactive Component が活性状態の時に設定されるマテリアル",
|
||||
|
||||
"ro_sim.effect_group.rule_inverted": "このルールの条件が反転されています",
|
||||
"ro_sim.effect_group.material.tooltip": "上記の Reactive Component がアクティブな時に設定されるマテリアル",
|
||||
"ro_sim.effect_group.rule_inverted": "このルールの条件は反転されています",
|
||||
"ro_sim.effect_group.rule_inverted.tooltip": "このルールは、いずれかの条件が満たされていない場合に適用されます",
|
||||
"ro_sim.effect_group.conditions": "条件"
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,6 @@
|
||||
"worldfixed.quest": "이 컴포넌트는 안드로이드 빌드를 대응하지 않습니다.",
|
||||
"worldfixed.normal": "이 오브젝트는 Constraint 을 사용하여 아바타에 고정하지 않는 이상 월드축에 고정됩니다.",
|
||||
"fpvisible.normal": "이 오브젝트는 일인칭 시점에서 보일 것입니다.",
|
||||
"fpvisible.NotUnderHead": "Head 본(Bone) 내의 오브젝트가 아닌 경우 작동하지 않습니다.",
|
||||
"fpvisible.quest": "이 컴포넌트는 오큘러스 퀘스트 단독 버전과 호환되지 않으며 영향을 미치지 않습니다.",
|
||||
"fpvisible.InPhysBoneChain": "이 객체는 Physics Bone 체인에 의해 컨트롤되므로 체인의 세부적인 선택이 불가능합니다. 대신 체인의 시작 부분을 선택하세요.",
|
||||
"blendshape.mesh": "메시",
|
||||
@ -144,4 +143,4 @@
|
||||
"control_group.is_synced": "동기화됨",
|
||||
"setup_outfit.err.unknown": "알 수 없는 오류",
|
||||
"ma_info.param_usage_ui.other_objects": "이 아바타안의 다른 오브젝트들"
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,6 @@
|
||||
"worldfixed.quest": "此组件未生效,因为它与 Oculus Quest 不兼容。",
|
||||
"worldfixed.normal": "当前对象将会固定于世界,除非您使用约束将它绑定在 Avatar 内。",
|
||||
"fpvisible.normal": "当前对象将在第一人称视角中可见。",
|
||||
"fpvisible.NotUnderHead": "此组件未生效,因为它需要放置在 Head 骨骼下。",
|
||||
"fpvisible.quest": "此组件未生效,因为它与 Oculus Quest 不兼容。",
|
||||
"fpvisible.InPhysBoneChain": "当前对象由 PhysicsBone 控制,可能无法在第一人称视角中可见;请指定 PhysicsBone 链的起点。",
|
||||
"blendshape.mesh": "网格",
|
||||
@ -227,7 +226,6 @@
|
||||
"setup_outfit.err.no_avatar_descriptor": "在 {0} 的父级中找不到 VRC Avatar Descriptor。请确保您的服装放置在 Avatar 内。",
|
||||
"setup_outfit.err.no_animator": "您的 Avatar 没有动画控制器 (Animator) 组件。",
|
||||
"setup_outfit.err.no_hips": "您的 Avatar 没有 Hips 骨骼。Setup Outfit 只能用于 humanoid Avatars。",
|
||||
"setup_outfit.err.no_outfit_hips": "无法识别服装的 Hips,已搜索包含以下名称的对象:",
|
||||
"move_independently.group-header": "要一起移动的对象",
|
||||
"scale_adjuster.scale": "调整比例",
|
||||
"scale_adjuster.adjust_children": "调整子级的位置",
|
||||
@ -237,4 +235,4 @@
|
||||
"ma_info.param_usage_ui.other_objects": "此 Avatar 上的其他对象",
|
||||
"ma_info.param_usage_ui.free_space": "未使用的参数空间 ({0} bits)",
|
||||
"ma_info.param_usage_ui.bits_template": "{0} ({1} bits)"
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,6 @@
|
||||
"worldfixed.quest": "此元件未生效,因為它與 Android 環境不相容。",
|
||||
"worldfixed.normal": "當前物件將會固定於世界,除非你使用約束將它綁在 Avatar 內。",
|
||||
"fpvisible.normal": "當前物件將在第一人稱視角中可見。",
|
||||
"fpvisible.NotUnderHead": "此元件未生效,因為它需要放置在 Head 骨骼下。",
|
||||
"fpvisible.quest": "此元件未生效,因為它與 Android 環境不相容。",
|
||||
"fpvisible.InPhysBoneChain": "當前物件由 Physics Bone 控制,可能無法在第一人稱視角中可見;請指定 Physics Bone 鏈的起點。",
|
||||
"blendshape.mesh": "網格",
|
||||
@ -187,6 +186,7 @@
|
||||
"menuitem.prop.type.tooltip": "此選單項的類型",
|
||||
"menuitem.prop.value": "參數值",
|
||||
"menuitem.prop.value.tooltip": "設定選單項觸發時的參數值",
|
||||
"menuitem.prop.automatic_value": "自動",
|
||||
"menuitem.prop.parameter": "參數",
|
||||
"menuitem.prop.label": "名稱",
|
||||
"menuitem.prop.submenu_asset": "子選單資源",
|
||||
@ -238,7 +238,6 @@
|
||||
"setup_outfit.err.no_avatar_descriptor": "在 {0} 的父級中找不到 VRC Avatar Descriptor。請確保你的服裝放置在 Avatar 裡。",
|
||||
"setup_outfit.err.no_animator": "你的 Avatar 沒有 Animator 元件。",
|
||||
"setup_outfit.err.no_hips": "你的 Avatar 沒有 Hips 骨骼。Setup Outfit 只能用於 humanoid Avatars。",
|
||||
"setup_outfit.err.no_outfit_hips": "識別不到服裝的 Hips,已搜尋包含以下名稱的物件:",
|
||||
"move_independently.group-header": "要一起移動的物件",
|
||||
"scale_adjuster.scale": "調整比例",
|
||||
"scale_adjuster.adjust_children": "調整子級的位置",
|
||||
@ -252,5 +251,26 @@
|
||||
"reactive_object.inverse": "反轉條件",
|
||||
"reactive_object.material-setter.set-to": "將材質設定為:",
|
||||
"menuitem.misc.add_toggle": "新增開關",
|
||||
"ro_sim.effect_group.material": "材質"
|
||||
}
|
||||
"ro_sim.open_debugger_button": "開啟響應除錯工具",
|
||||
"ro_sim.window.title": "MA 響應除錯工具",
|
||||
"ro_sim.header.inspecting": "檢視物件",
|
||||
"ro_sim.header.clear_overrides": "清除所有覆寫",
|
||||
"ro_sim.header.object_state": "物件狀態",
|
||||
"ro_sim.state.active": "啟用",
|
||||
"ro_sim.state.inactive": "停用",
|
||||
"ro_sim.header.override_gameobject_state": "覆寫物件狀態",
|
||||
"ro_sim.header.override_menuitem_state": "覆寫選單項狀態",
|
||||
"ro_sim.affected_by.title": "受到以下影響:",
|
||||
"ro_sim.effect_group.controls_obj_state": "控制物件狀態為:",
|
||||
"ro_sim.effect_group.target_component": "目標元件",
|
||||
"ro_sim.effect_group.target_component.tooltip": "受到 Reactive Component 影響的元件",
|
||||
"ro_sim.effect_group.property": "屬性",
|
||||
"ro_sim.effect_group.property.tooltip": "受到 Reactive Component 影響的目標元件屬性",
|
||||
"ro_sim.effect_group.value": "值",
|
||||
"ro_sim.effect_group.value.tooltip": "Reactive Component 啟用時,屬性將被設為此值",
|
||||
"ro_sim.effect_group.material": "材質",
|
||||
"ro_sim.effect_group.material.tooltip": "Reactive Component 啟用時,將被設在目標元件上的材質",
|
||||
"ro_sim.effect_group.rule_inverted": "規則的條件已反轉",
|
||||
"ro_sim.effect_group.rule_inverted.tooltip": "這條規則將在未達成其任一條件時套用。",
|
||||
"ro_sim.effect_group.conditions": "條件"
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using nadena.dev.modular_avatar.core.menu;
|
||||
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
||||
using nadena.dev.ndmf;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
@ -102,7 +103,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
PushControl(control);
|
||||
}
|
||||
|
||||
if (_menuToInstallerMap.TryGetValue(expMenu, out var installers))
|
||||
if (_menuToInstallerMap.TryGetValue(ObjectRegistry.GetReference(expMenu), out var installers))
|
||||
{
|
||||
foreach (var installer in installers)
|
||||
{
|
||||
@ -311,7 +312,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
// initial validation
|
||||
if (installer.menuToAppend == null && installer.GetComponent<MenuSource>() == null) return;
|
||||
|
||||
var target = installer.installTargetMenu ? (object) installer.installTargetMenu : RootMenuKey;
|
||||
var target = installer.installTargetMenu ? (object) ObjectRegistry.GetReference(installer.installTargetMenu) : RootMenuKey;
|
||||
if (!_targetMenuToInstaller.TryGetValue(target, out var targets))
|
||||
{
|
||||
targets = new List<ModularAvatarMenuInstaller>();
|
||||
@ -366,7 +367,7 @@ namespace nadena.dev.modular_avatar.core.editor.menu
|
||||
}
|
||||
|
||||
// Some menu installers may be bound to the root menu _asset_ directly.
|
||||
if (menuToInstallerFiltered.TryGetValue(menu, out var installers))
|
||||
if (menuToInstallerFiltered.TryGetValue(ObjectRegistry.GetReference(menu), out var installers))
|
||||
{
|
||||
foreach (var installer in installers)
|
||||
{
|
||||
|
@ -237,8 +237,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
private bool? ProbeWriteDefaults(AnimatorController controller)
|
||||
internal static bool? ProbeWriteDefaults(AnimatorController controller)
|
||||
{
|
||||
if (controller == null) return null;
|
||||
|
||||
bool hasWDOn = false;
|
||||
bool hasWDOff = false;
|
||||
|
||||
|
83
Editor/ObjectReferenceFixer.cs
Normal file
83
Editor/ObjectReferenceFixer.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using nadena.dev.ndmf.preview;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
public static class ObjectReferenceFixer
|
||||
{
|
||||
private static ComputeContext _context;
|
||||
|
||||
private static PrefabStage _lastStage;
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
private static void Init()
|
||||
{
|
||||
EditorApplication.delayCall += ProcessObjectReferences;
|
||||
EditorApplication.update += () =>
|
||||
{
|
||||
if (PrefabStageUtility.GetCurrentPrefabStage() != _lastStage) _context?.Invalidate?.Invoke();
|
||||
};
|
||||
}
|
||||
|
||||
private static void ProcessObjectReferences()
|
||||
{
|
||||
_lastStage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||
|
||||
_context = new ComputeContext("ObjectReferenceFixer");
|
||||
_context.InvokeOnInvalidate<object>(typeof(ObjectReferenceFixer), _ => ProcessObjectReferences());
|
||||
|
||||
IEnumerable<IHaveObjReferences> withReferences = _context.GetComponentsByType<IHaveObjReferences>();
|
||||
if (_lastStage != null)
|
||||
withReferences =
|
||||
withReferences.Concat(
|
||||
_context.GetComponentsInChildren<IHaveObjReferences>(_lastStage.prefabContentsRoot, true)
|
||||
);
|
||||
|
||||
foreach (var obj in withReferences)
|
||||
{
|
||||
var component = obj as Component;
|
||||
if (component == null) continue;
|
||||
|
||||
var avatar = _context.GetAvatarRoot(component.gameObject);
|
||||
if (avatar == null) continue;
|
||||
|
||||
var references = _context.Observe(component,
|
||||
c => ((IHaveObjReferences)c).GetObjectReferences().Select(
|
||||
r => (r.targetObject, r.referencePath, r)
|
||||
),
|
||||
Enumerable.SequenceEqual
|
||||
);
|
||||
|
||||
var dirty = false;
|
||||
|
||||
foreach (var (targetObject, referencePath, objRef) in references)
|
||||
{
|
||||
if (targetObject == null) continue;
|
||||
_context.ObservePath(targetObject.transform);
|
||||
|
||||
if (!targetObject.transform.IsChildOf(avatar.transform)) continue;
|
||||
|
||||
if (objRef.IsConsistent(avatar)) continue;
|
||||
|
||||
if (!dirty)
|
||||
{
|
||||
dirty = true;
|
||||
Undo.RecordObject(component, "");
|
||||
}
|
||||
|
||||
objRef.Set(targetObject);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
EditorUtility.SetDirty(component);
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Editor/ObjectReferenceFixer.cs.meta
Normal file
3
Editor/ObjectReferenceFixer.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eba8a4ec992b42d894f9206c642c49ad
|
||||
timeCreated: 1725229885
|
@ -45,7 +45,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
var converters = context.AvatarRootObject.GetComponentsInChildren<ModularAvatarConvertConstraints>(true)
|
||||
.Select(c => c.gameObject)
|
||||
.ToHashSet(new ObjectIdentityComparer<GameObject>());
|
||||
.ToHashSet();
|
||||
if (converters.Count == 0) return;
|
||||
|
||||
var constraintGameObjects = context.AvatarRootObject.GetComponentsInChildren<IConstraint>(true)
|
||||
|
@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
using Object = UnityEngine.Object;
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
using VRC.SDK3.Dynamics.PhysBone.Components;
|
||||
#endif
|
||||
@ -134,7 +134,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MissingComponentException _)
|
||||
catch (MissingComponentException)
|
||||
{
|
||||
// No animator? weird. Move on.
|
||||
}
|
||||
@ -221,7 +221,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
if (!referencedGameObjects.Contains(go))
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(go);
|
||||
Object.DestroyImmediate(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,20 +34,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
hidden = true;
|
||||
}
|
||||
|
||||
var type = AnimatorControllerParameterType.Bool;
|
||||
|
||||
if (type != AnimatorControllerParameterType.Float &&
|
||||
(_component.Control.value > 1.01 || _component.Control.value < -0.01))
|
||||
type = AnimatorControllerParameterType.Int;
|
||||
|
||||
if (Mathf.Abs(Mathf.Round(_component.Control.value) - _component.Control.value) > 0.01f)
|
||||
type = AnimatorControllerParameterType.Float;
|
||||
|
||||
yield return new ProvidedParameter(
|
||||
name,
|
||||
ParameterNamespace.Animator,
|
||||
_component, PluginDefinition.Instance, type)
|
||||
_component, PluginDefinition.Instance, _component.AnimatorControllerParameterType)
|
||||
{
|
||||
ExpandTypeOnConflict = true,
|
||||
WantSynced = _component.isSynced,
|
||||
IsHidden = hidden,
|
||||
DefaultValue = _component.isDefault ? _component.Control.value : null
|
||||
|
@ -109,7 +109,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
return uxml != null && EditorStyles.label != null;
|
||||
}
|
||||
catch (NullReferenceException _)
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using nadena.dev.ndmf.preview;
|
||||
using UnityEngine;
|
||||
using nadena.dev.ndmf.preview.trace;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
@ -13,54 +13,118 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
public PropCache<Key, Value> Owner;
|
||||
public Key Key;
|
||||
public Value Value;
|
||||
public string DebugName;
|
||||
public int Generation;
|
||||
}
|
||||
|
||||
private readonly string _debugName;
|
||||
private readonly Func<ComputeContext, Key, Value> _operator;
|
||||
private readonly Func<Value, Value, bool> _equalityComparer;
|
||||
private readonly Dictionary<Key, CacheEntry> _cache = new();
|
||||
|
||||
public PropCache(Func<ComputeContext, Key, Value> operatorFunc, Func<Value, Value, bool> equalityComparer = null)
|
||||
private static int _generation = 0;
|
||||
|
||||
public PropCache(string debugName, Func<ComputeContext, Key, Value> operatorFunc,
|
||||
Func<Value, Value, bool> equalityComparer = null)
|
||||
{
|
||||
_debugName = debugName;
|
||||
_operator = operatorFunc;
|
||||
_equalityComparer = equalityComparer;
|
||||
}
|
||||
|
||||
private static void InvalidateEntry(CacheEntry entry)
|
||||
{
|
||||
var newGenContext = new ComputeContext("PropCache for key " + entry.Key);
|
||||
var newGenContext = new ComputeContext("PropCache/" + entry.DebugName + " key " + FormatKey(entry.Key) + " gen=" + _generation++);
|
||||
var newValue = entry.Owner._operator(newGenContext, entry.Key);
|
||||
if (entry.Owner._equalityComparer != null && entry.Owner._equalityComparer(entry.Value, newValue))
|
||||
if (!entry.ObserverContext.IsInvalidated && entry.Owner._equalityComparer != null && entry.Owner._equalityComparer(entry.Value, newValue))
|
||||
{
|
||||
TraceBuffer.RecordTraceEvent(
|
||||
"PropCache.InvalidateEntry",
|
||||
(ev) => $"[PropCache/{ev.Arg0}] Value did not change, retaining result (new gen={ev.Arg1})",
|
||||
entry.DebugName, entry.Generation
|
||||
);
|
||||
|
||||
entry.GenerateContext = newGenContext;
|
||||
entry.GenerateContext.InvokeOnInvalidate(entry, InvalidateEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
var trace = TraceBuffer.RecordTraceEvent(
|
||||
"PropCache.InvalidateEntry",
|
||||
(ev) => $"[PropCache/{ev.Arg0}] Value changed, invalidating",
|
||||
entry.DebugName
|
||||
);
|
||||
|
||||
entry.Owner._cache.Remove(entry.Key);
|
||||
entry.ObserverContext.Invalidate();
|
||||
using (trace.Scope()) entry.ObserverContext.Invalidate();
|
||||
}
|
||||
|
||||
public Value Get(ComputeContext context, Key key)
|
||||
{
|
||||
TraceEvent ev;
|
||||
var formattedKey = FormatKey(key);
|
||||
if (!_cache.TryGetValue(key, out var entry) || entry.GenerateContext.IsInvalidated)
|
||||
{
|
||||
var subContext = new ComputeContext("PropCache for key " + key);
|
||||
var curGen = _generation++;
|
||||
|
||||
var subContext = new ComputeContext("PropCache/" + _debugName + " key " + formattedKey + " gen=" + curGen);
|
||||
entry = new CacheEntry
|
||||
{
|
||||
GenerateContext = subContext,
|
||||
ObserverContext = new ComputeContext("Observer for PropCache for key " + key),
|
||||
ObserverContext = new ComputeContext("Observer for PropCache/" + _debugName + " for key " +
|
||||
formattedKey + " gen=" + curGen),
|
||||
Owner = this,
|
||||
Key = key,
|
||||
Value = _operator(subContext, key)
|
||||
DebugName = _debugName,
|
||||
Generation = curGen
|
||||
};
|
||||
|
||||
ev = TraceBuffer.RecordTraceEvent(
|
||||
"PropCache.Get",
|
||||
(ev) =>
|
||||
{
|
||||
var entry_ = (CacheEntry)ev.Arg0;
|
||||
return
|
||||
$"[PropCache/{entry_.DebugName}] Cache miss for key {entry_.Key} gen={entry_.Generation} from context {ev.Arg1}";
|
||||
},
|
||||
entry, context
|
||||
);
|
||||
|
||||
_cache[key] = entry;
|
||||
|
||||
subContext.InvokeOnInvalidate(entry, InvalidateEntry);
|
||||
using (ev.Scope())
|
||||
{
|
||||
entry.Value = _operator(subContext, key);
|
||||
entry.GenerateContext.InvokeOnInvalidate(entry, InvalidateEntry);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ev = TraceBuffer.RecordTraceEvent(
|
||||
"PropCache.Get",
|
||||
(ev) =>
|
||||
{
|
||||
var entry_ = (CacheEntry) ev.Arg0;
|
||||
return $"[PropCache/{entry_.DebugName}] Cache hit for key {entry_.Key} gen={entry_.Generation} from context {ev.Arg1}";
|
||||
},
|
||||
entry, context
|
||||
);
|
||||
}
|
||||
|
||||
entry.ObserverContext.Invalidates(context);
|
||||
|
||||
return entry.Value;
|
||||
}
|
||||
|
||||
private static string FormatKey(object obj)
|
||||
{
|
||||
if (obj is UnityEngine.Object unityObj)
|
||||
{
|
||||
return $"{unityObj.GetHashCode()}#{unityObj}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "" + obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -63,6 +63,17 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
_computeContext.Observe(mami, c => (c.Control?.parameter, c.Control?.type, c.Control?.value, c.isDefault));
|
||||
|
||||
var mami_condition = ParameterAssignerPass.AssignMenuItemParameter(mami, _simulationInitialStates);
|
||||
|
||||
if (mami_condition != null &&
|
||||
ForceMenuItems.TryGetValue(mami_condition.Parameter, out var forcedMenuItem))
|
||||
{
|
||||
var enable = forcedMenuItem == mami;
|
||||
mami_condition.InitialValue = 0.5f;
|
||||
mami_condition.ParameterValueLo = enable ? 0 : 999f;
|
||||
mami_condition.ParameterValueHi = 1000;
|
||||
mami_condition.IsConstant = true;
|
||||
}
|
||||
|
||||
if (mami_condition != null) conditions.Add(mami_condition);
|
||||
}
|
||||
|
||||
@ -71,7 +82,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
Parameter = GetActiveSelfProxy(cursor.gameObject),
|
||||
DebugName = cursor.gameObject.name,
|
||||
IsConstant = false,
|
||||
InitialValue = cursor.gameObject.activeSelf ? 1.0f : 0.0f,
|
||||
InitialValue = _computeContext.Observe(cursor.gameObject, go => go.activeSelf) ? 1.0f : 0.0f,
|
||||
ParameterValueLo = 0.5f,
|
||||
ParameterValueHi = float.PositiveInfinity,
|
||||
ReferenceObject = cursor.gameObject,
|
||||
@ -129,7 +140,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
|
||||
var action = ObjectRule(key, changer, value);
|
||||
action.Inverted = changer.Inverted;
|
||||
action.Inverted = _computeContext.Observe(changer, c => c.Inverted);
|
||||
var isCurrentlyActive = changer.gameObject.activeInHierarchy;
|
||||
|
||||
if (shape.ChangeType == ShapeChangeType.Delete)
|
||||
@ -186,7 +197,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
|
||||
var action = ObjectRule(key, setter, obj.Material);
|
||||
action.Inverted = setter.Inverted;
|
||||
action.Inverted = _computeContext.Observe(setter, c => c.Inverted);
|
||||
|
||||
if (group.actionGroups.Count == 0)
|
||||
group.actionGroups.Add(action);
|
||||
@ -217,13 +228,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
if (!objectGroups.TryGetValue(key, out var group))
|
||||
{
|
||||
group = new AnimatedProperty(key, target.activeSelf ? 1 : 0);
|
||||
var active = _computeContext.Observe(target, t => t.activeSelf);
|
||||
group = new AnimatedProperty(key, active ? 1 : 0);
|
||||
objectGroups[key] = group;
|
||||
}
|
||||
|
||||
var value = obj.Active ? 1 : 0;
|
||||
var action = ObjectRule(key, toggle, value);
|
||||
action.Inverted = toggle.Inverted;
|
||||
action.Inverted = _computeContext.Observe(toggle, c => c.Inverted);
|
||||
|
||||
if (group.actionGroups.Count == 0)
|
||||
group.actionGroups.Add(action);
|
||||
|
@ -20,6 +20,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
private Dictionary<string, float> _simulationInitialStates;
|
||||
|
||||
public ImmutableDictionary<string, float> ForcePropertyOverrides { get; set; } = ImmutableDictionary<string, float>.Empty;
|
||||
|
||||
public ImmutableDictionary<string, ModularAvatarMenuItem> ForceMenuItems { get; set; } =
|
||||
ImmutableDictionary<string, ModularAvatarMenuItem>.Empty;
|
||||
|
||||
public ReactiveObjectAnalyzer(ndmf.BuildContext context)
|
||||
{
|
||||
@ -46,8 +49,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
var mami = obj?.GetComponent<ModularAvatarMenuItem>();
|
||||
if (mami == null) return null;
|
||||
|
||||
return ParameterAssignerPass.AssignMenuItemParameter(mami, _simulationInitialStates)?.Parameter;
|
||||
|
||||
return ParameterAssignerPass.AssignMenuItemParameter(mami, _simulationInitialStates, ForceMenuItems)
|
||||
?.Parameter;
|
||||
}
|
||||
|
||||
public struct AnalysisResult
|
||||
@ -63,11 +67,13 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
if (_analysisCache == null)
|
||||
{
|
||||
_analysisCache = new PropCache<GameObject, AnalysisResult>((ctx, root) =>
|
||||
_analysisCache = new PropCache<GameObject, AnalysisResult>("ROAnalyzer", (ctx, root) =>
|
||||
{
|
||||
var analysis = new ReactiveObjectAnalyzer(ctx);
|
||||
analysis.ForcePropertyOverrides = ctx.Observe(ROSimulator.PropertyOverrides, a=>a, (a,b) => false)
|
||||
?? ImmutableDictionary<string, float>.Empty;
|
||||
analysis.ForceMenuItems = ctx.Observe(ROSimulator.MenuItemOverrides, a => a, (a, b) => false)
|
||||
?? ImmutableDictionary<string, ModularAvatarMenuItem>.Empty;
|
||||
return analysis.Analyze(root);
|
||||
});
|
||||
}
|
||||
@ -101,7 +107,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
FindMaterialSetters(shapes, root);
|
||||
|
||||
ApplyInitialStateOverrides(shapes);
|
||||
AnalyzeConstants(shapes);
|
||||
AnalyzeConstants(shapes);
|
||||
ResolveToggleInitialStates(shapes);
|
||||
PreprocessShapes(shapes, out result.InitialStates, out result.DeletedShapes);
|
||||
result.Shapes = shapes;
|
||||
@ -151,9 +157,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
if (condition.ReferenceObject != null && !toggledObjects.Contains(condition.ReferenceObject))
|
||||
condition.IsConstant = asc.AnimationDatabase.ClipsForPath(asc.PathMappings.GetObjectIdentifier(condition.ReferenceObject)).IsEmpty;
|
||||
|
||||
var i = 0;
|
||||
// Remove redundant active conditions.
|
||||
int retain = 0;
|
||||
actionGroup.ControllingConditions.RemoveAll(c => c.IsConstant && c.InitiallyActive);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
private HashSet<string> activeProps = new();
|
||||
|
||||
private AnimationClip _initialStateClip;
|
||||
private bool _writeDefaults;
|
||||
|
||||
public ReactiveObjectPass(ndmf.BuildContext context)
|
||||
{
|
||||
@ -33,6 +34,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
internal void Execute()
|
||||
{
|
||||
// Having a WD OFF layer after WD ON layers can break WD. We match the behavior of the existing states,
|
||||
// and if mixed, use WD ON to maximize compatibility.
|
||||
_writeDefaults = MergeAnimatorProcessor.ProbeWriteDefaults(FindFxController().animatorController as AnimatorController) ?? true;
|
||||
|
||||
var analysis = new ReactiveObjectAnalyzer(context).Analyze(context.AvatarRootObject);
|
||||
|
||||
var shapes = analysis.Shapes;
|
||||
@ -75,8 +80,10 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
if (condition.IsConstant) continue;
|
||||
|
||||
if (!initialValues.ContainsKey(condition.Parameter))
|
||||
if (!initialValues.TryGetValue(condition.Parameter, out var curVal) || curVal < -999f)
|
||||
{
|
||||
initialValues[condition.Parameter] = condition.InitialValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +101,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
if (initialStateHolder == null) return;
|
||||
|
||||
_initialStateClip = new AnimationClip();
|
||||
_initialStateClip.name = "MA Shape Changer Defaults";
|
||||
_initialStateClip.name = "Reactive Component Defaults";
|
||||
initialStateHolder.CurrentClip = _initialStateClip;
|
||||
|
||||
foreach (var (key, initialState) in initialStates)
|
||||
@ -262,7 +269,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
var asc = context.Extension<AnimationServicesContext>();
|
||||
var asm = new AnimatorStateMachine();
|
||||
asm.name = "MA Shape Changer " + info.TargetProp.TargetObject.name;
|
||||
|
||||
// Workaround for the warning: "'.' is not allowed in State name"
|
||||
asm.name = "RC " + info.TargetProp.TargetObject.name.Replace(".", "_");
|
||||
|
||||
var x = 200;
|
||||
var y = 0;
|
||||
@ -273,7 +282,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var initial = new AnimationClip();
|
||||
var initialState = new AnimatorState();
|
||||
initialState.motion = initial;
|
||||
initialState.writeDefaultValues = false;
|
||||
initialState.writeDefaultValues = _writeDefaults;
|
||||
initialState.name = "<default>";
|
||||
asm.defaultState = initialState;
|
||||
|
||||
@ -291,7 +300,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var transitionBuffer = new List<(AnimatorState, List<AnimatorStateTransition>)>();
|
||||
var entryTransitions = new List<AnimatorTransition>();
|
||||
|
||||
transitionBuffer.Add((initialState, new List<AnimatorStateTransition>()));
|
||||
var initialStateTransitionList = new List<AnimatorStateTransition>();
|
||||
transitionBuffer.Add((initialState, initialStateTransitionList));
|
||||
|
||||
foreach (var group in info.actionGroups.Skip(lastConstant))
|
||||
{
|
||||
@ -311,40 +321,40 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
var conditions = GetTransitionConditions(asc, group);
|
||||
|
||||
foreach (var (st, transitions) in transitionBuffer)
|
||||
if (!group.Inverted)
|
||||
{
|
||||
if (!group.Inverted)
|
||||
var transition = new AnimatorStateTransition
|
||||
{
|
||||
var transition = new AnimatorStateTransition
|
||||
isExit = true,
|
||||
hasExitTime = false,
|
||||
duration = 0,
|
||||
hasFixedDuration = true,
|
||||
conditions = (AnimatorCondition[])conditions.Clone()
|
||||
};
|
||||
initialStateTransitionList.Add(transition);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var cond in conditions)
|
||||
{
|
||||
initialStateTransitionList.Add(new AnimatorStateTransition
|
||||
{
|
||||
isExit = true,
|
||||
hasExitTime = false,
|
||||
duration = 0,
|
||||
hasFixedDuration = true,
|
||||
conditions = (AnimatorCondition[])conditions.Clone()
|
||||
};
|
||||
transitions.Add(transition);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var cond in conditions)
|
||||
{
|
||||
transitions.Add(new AnimatorStateTransition
|
||||
{
|
||||
isExit = true,
|
||||
hasExitTime = false,
|
||||
duration = 0,
|
||||
hasFixedDuration = true,
|
||||
conditions = new[] { InvertCondition(cond) }
|
||||
});
|
||||
}
|
||||
conditions = new[] { InvertCondition(cond) }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var state = new AnimatorState();
|
||||
state.name = group.ControllingConditions[0].DebugName;
|
||||
|
||||
// Workaround for the warning: "'.' is not allowed in State name"
|
||||
state.name = group.ControllingConditions[0].DebugName.Replace(".", "_");
|
||||
|
||||
state.motion = clip;
|
||||
state.writeDefaultValues = false;
|
||||
state.writeDefaultValues = _writeDefaults;
|
||||
states.Add(new ChildAnimatorState
|
||||
{
|
||||
position = new Vector3(x, y),
|
||||
@ -520,13 +530,13 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
private void ApplyController(AnimatorStateMachine asm, string layerName)
|
||||
{
|
||||
var fx = context.AvatarDescriptor.baseAnimationLayers
|
||||
.FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX);
|
||||
var fx = FindFxController();
|
||||
|
||||
if (fx.animatorController == null)
|
||||
{
|
||||
throw new InvalidOperationException("No FX layer found");
|
||||
}
|
||||
|
||||
|
||||
if (!context.IsTemporaryAsset(fx.animatorController))
|
||||
{
|
||||
throw new InvalidOperationException("FX layer is not a temporary asset");
|
||||
@ -557,10 +567,18 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
new AnimatorControllerLayer
|
||||
{
|
||||
stateMachine = asm,
|
||||
name = "MA Shape Changer " + layerName,
|
||||
name = "RC " + layerName,
|
||||
defaultWeight = 1
|
||||
}
|
||||
).ToArray();
|
||||
}
|
||||
|
||||
private VRCAvatarDescriptor.CustomAnimLayer FindFxController()
|
||||
{
|
||||
var fx = context.AvatarDescriptor.baseAnimationLayers
|
||||
.FirstOrDefault(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX);
|
||||
|
||||
return fx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
private const string PREFIX = "m_Materials.Array.data[";
|
||||
|
||||
private PropCache<Renderer, ImmutableList<(int, Material)>> _cache = new(
|
||||
GetMaterialOverridesForRenderer, Enumerable.SequenceEqual
|
||||
"GetMaterialOverridesForRenderer", GetMaterialOverridesForRenderer, Enumerable.SequenceEqual
|
||||
);
|
||||
|
||||
private static ImmutableList<(int, Material)> GetMaterialOverridesForRenderer(ComputeContext ctx, Renderer r)
|
||||
|
@ -42,6 +42,9 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
foreach (var renderer in renderers)
|
||||
{
|
||||
// For now, the preview system only supports MeshRenderer and SkinnedMeshRenderer
|
||||
if (renderer is not MeshRenderer and not SkinnedMeshRenderer) continue;
|
||||
|
||||
bool currentlyEnabled = context.ActiveInHierarchy(renderer.gameObject);
|
||||
|
||||
bool overrideEnabled = true;
|
||||
|
@ -39,7 +39,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal void TestExecute(ndmf.BuildContext context)
|
||||
{
|
||||
Execute(context);
|
||||
}
|
||||
|
||||
protected override void Execute(ndmf.BuildContext context)
|
||||
{
|
||||
@ -47,11 +52,14 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
var paramIndex = 0;
|
||||
|
||||
var declaredParams = context.AvatarDescriptor.expressionParameters.parameters.Select(p => p.name)
|
||||
.ToHashSet();
|
||||
var declaredParams = context.AvatarDescriptor.expressionParameters.parameters
|
||||
.GroupBy(p => p.name).Select(l => l.First())
|
||||
.ToDictionary(p => p.name);
|
||||
|
||||
Dictionary<string, VRCExpressionParameters.Parameter> newParameters = new();
|
||||
Dictionary<string, int> nextParamValue = new();
|
||||
|
||||
Dictionary<string, List<ModularAvatarMenuItem>> _mamiByParam = new();
|
||||
foreach (var mami in context.AvatarRootTransform.GetComponentsInChildren<ModularAvatarMenuItem>(true))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mami.Control?.parameter?.name))
|
||||
@ -64,51 +72,110 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
name = $"__MA/AutoParam/{mami.gameObject.name}${paramIndex++}"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var paramName = mami.Control.parameter.name;
|
||||
|
||||
if (!declaredParams.Contains(paramName))
|
||||
if (!_mamiByParam.TryGetValue(paramName, out var mamiList))
|
||||
{
|
||||
newParameters.TryGetValue(paramName, out var existingNewParam);
|
||||
var wantedType = existingNewParam?.valueType ?? VRCExpressionParameters.ValueType.Bool;
|
||||
mamiList = new List<ModularAvatarMenuItem>();
|
||||
_mamiByParam[paramName] = mamiList;
|
||||
}
|
||||
|
||||
if (wantedType != VRCExpressionParameters.ValueType.Float &&
|
||||
(mami.Control.value > 1.01 || mami.Control.value < -0.01))
|
||||
wantedType = VRCExpressionParameters.ValueType.Int;
|
||||
mamiList.Add(mami);
|
||||
}
|
||||
|
||||
if (Mathf.Abs(Mathf.Round(mami.Control.value) - mami.Control.value) > 0.01f)
|
||||
wantedType = VRCExpressionParameters.ValueType.Float;
|
||||
foreach (var (paramName, list) in _mamiByParam)
|
||||
{
|
||||
// Assign automatic values first
|
||||
int? defaultValue = null;
|
||||
if (declaredParams.TryGetValue(paramName, out var p))
|
||||
{
|
||||
defaultValue = (int) p.defaultValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var floatDefault = list.FirstOrDefault(m => m.isDefault && !m.automaticValue)?.Control?.value;
|
||||
if (floatDefault.HasValue) defaultValue = (int) floatDefault.Value;
|
||||
|
||||
if (existingNewParam == null)
|
||||
if (list.Count == 1 && list[0].isDefault && list[0].automaticValue)
|
||||
// If we have only a single entry, it's probably an on-off toggle, so we'll implicitly let 1
|
||||
// be the 'selected' default value (if this is default and automatic value)
|
||||
defaultValue = 1;
|
||||
}
|
||||
|
||||
HashSet<int> usedValues = new();
|
||||
if (defaultValue.HasValue) usedValues.Add(defaultValue.Value);
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (!item.automaticValue)
|
||||
{
|
||||
existingNewParam = new VRCExpressionParameters.Parameter
|
||||
usedValues.Add((int)item.Control.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!defaultValue.HasValue)
|
||||
{
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
if (!usedValues.Contains(i))
|
||||
{
|
||||
name = paramName,
|
||||
valueType = wantedType,
|
||||
saved = mami.isSaved,
|
||||
defaultValue = -1,
|
||||
networkSynced = mami.isSynced
|
||||
};
|
||||
newParameters[paramName] = existingNewParam;
|
||||
defaultValue = i;
|
||||
usedValues.Add(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
var nextValue = 1;
|
||||
|
||||
var valueType = VRCExpressionParameters.ValueType.Bool;
|
||||
var isSaved = false;
|
||||
var isSynced = false;
|
||||
|
||||
foreach (var mami in list)
|
||||
{
|
||||
if (mami.automaticValue)
|
||||
{
|
||||
existingNewParam.valueType = wantedType;
|
||||
if (mami.isDefault)
|
||||
{
|
||||
mami.Control.value = defaultValue.GetValueOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
while (usedValues.Contains(nextValue)) nextValue++;
|
||||
|
||||
mami.Control.value = nextValue;
|
||||
usedValues.Add(nextValue);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: warn on inconsistent configuration
|
||||
existingNewParam.saved = existingNewParam.saved || mami.isSaved;
|
||||
existingNewParam.networkSynced = existingNewParam.networkSynced || mami.isSynced;
|
||||
existingNewParam.defaultValue = mami.isDefault ? mami.Control.value : existingNewParam.defaultValue;
|
||||
var newValueType = mami.ExpressionParametersValueType;
|
||||
if (valueType == VRCExpressionParameters.ValueType.Bool || newValueType == VRCExpressionParameters.ValueType.Float)
|
||||
{
|
||||
valueType = newValueType;
|
||||
}
|
||||
|
||||
isSaved |= mami.isSaved;
|
||||
isSynced |= mami.isSynced;
|
||||
}
|
||||
|
||||
if (!declaredParams.ContainsKey(paramName))
|
||||
{
|
||||
var newParam = new VRCExpressionParameters.Parameter
|
||||
{
|
||||
name = paramName,
|
||||
valueType = valueType,
|
||||
saved = isSaved,
|
||||
defaultValue = defaultValue.GetValueOrDefault(),
|
||||
networkSynced = isSynced
|
||||
};
|
||||
newParameters[paramName] = newParam;
|
||||
}
|
||||
}
|
||||
|
||||
if (newParameters.Count > 0)
|
||||
{
|
||||
foreach (var p in newParameters)
|
||||
if (p.Value.defaultValue < 0)
|
||||
p.Value.defaultValue = 0;
|
||||
|
||||
var expParams = context.AvatarDescriptor.expressionParameters;
|
||||
if (!context.IsTemporaryAsset(expParams))
|
||||
{
|
||||
@ -120,19 +187,35 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
internal static ControlCondition AssignMenuItemParameter(ModularAvatarMenuItem mami, Dictionary<string, float> simulationInitialStates = null)
|
||||
internal static ControlCondition AssignMenuItemParameter(
|
||||
ModularAvatarMenuItem mami,
|
||||
Dictionary<string, float> simulationInitialStates = null,
|
||||
IDictionary<string, ModularAvatarMenuItem> isDefaultOverrides = null,
|
||||
bool? forceSimulation = null
|
||||
)
|
||||
{
|
||||
var isSimulation = (simulationInitialStates != null || forceSimulation == true);
|
||||
|
||||
var paramName = mami?.Control?.parameter?.name;
|
||||
if (mami?.Control != null && simulationInitialStates != null && ShouldAssignParametersToMami(mami))
|
||||
if (mami?.Control != null && isSimulation && ShouldAssignParametersToMami(mami))
|
||||
{
|
||||
paramName = "___AutoProp/" + mami.Control?.parameter?.name;
|
||||
paramName = mami.Control?.parameter?.name;
|
||||
if (string.IsNullOrEmpty(paramName)) paramName = "___AutoProp/" + mami.GetInstanceID();
|
||||
|
||||
if (mami.isDefault)
|
||||
if (simulationInitialStates != null)
|
||||
{
|
||||
simulationInitialStates[paramName] = mami.Control.value;
|
||||
} else if (!simulationInitialStates.ContainsKey(paramName))
|
||||
{
|
||||
simulationInitialStates[paramName] = -999;
|
||||
var isDefault = mami.isDefault;
|
||||
if (isDefaultOverrides?.TryGetValue(paramName, out var target) == true)
|
||||
isDefault = ReferenceEquals(mami, target);
|
||||
|
||||
if (isDefault)
|
||||
{
|
||||
simulationInitialStates[paramName] = mami.Control.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
simulationInitialStates.TryAdd(paramName, -999);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,9 +226,12 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
Parameter = paramName,
|
||||
DebugName = mami.gameObject.name,
|
||||
IsConstant = false,
|
||||
InitialValue = mami.isDefault ? mami.Control.value : -999, // TODO
|
||||
ParameterValueLo = mami.Control.value - 0.5f,
|
||||
ParameterValueHi = mami.Control.value + 0.5f,
|
||||
// Note: This slightly odd-looking value is key to making the Auto checkbox work for editor previews;
|
||||
// we basically force-disable any conditions for nonselected menu items and force-enable any for default
|
||||
// menu items.
|
||||
InitialValue = mami.isDefault ? mami.Control.value : -999,
|
||||
ParameterValueLo = mami.Control.value - 0.005f,
|
||||
ParameterValueHi = mami.Control.value + 0.005f,
|
||||
DebugReference = mami,
|
||||
};
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
n_nrm[i] = o_nrm[newToOrigVertIndex[i]];
|
||||
n_tan[i] = o_tan[newToOrigVertIndex[i]];
|
||||
}
|
||||
catch (IndexOutOfRangeException e)
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
@ -250,7 +250,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
List<int> n2o = new List<int>(toRetainVertices.Length);
|
||||
List<int> o2n = new List<int>(toRetainVertices.Length);
|
||||
int i = 0;
|
||||
|
||||
for (int j = 0; j < toRetainVertices.Length; j++)
|
||||
{
|
||||
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using nadena.dev.modular_avatar.core.editor.Simulator;
|
||||
using nadena.dev.ndmf.preview;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
@ -60,8 +59,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
private PropCache<GameObject, ImmutableDictionary<SkinnedMeshRenderer, ImmutableList<(int, float)>>>
|
||||
_blendshapeCache = new(ShapesForAvatar);
|
||||
private readonly PropCache<GameObject, ImmutableDictionary<SkinnedMeshRenderer, ImmutableList<(int, float)>>>
|
||||
_blendshapeCache = new("ShapesForAvatar", ShapesForAvatar);
|
||||
|
||||
private static ImmutableDictionary<SkinnedMeshRenderer, ImmutableList<(int, float)>> ShapesForAvatar(ComputeContext context, GameObject avatarRoot)
|
||||
{
|
||||
@ -75,7 +74,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
ImmutableDictionary<SkinnedMeshRenderer, ImmutableList<(int, float)>>.Builder rendererStates =
|
||||
ImmutableDictionary.CreateBuilder<SkinnedMeshRenderer, ImmutableList<(int, float)>>(
|
||||
new ObjectIdentityComparer<SkinnedMeshRenderer>()
|
||||
|
||||
);
|
||||
var avatarRootTransform = avatarRoot.transform;
|
||||
|
||||
@ -133,12 +132,18 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
.ToImmutableList();
|
||||
}
|
||||
|
||||
public Task<IRenderFilterNode> Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context)
|
||||
public async Task<IRenderFilterNode> Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context)
|
||||
{
|
||||
var shapeValues = group.GetData<StaticContext>();
|
||||
var node = new Node(shapeValues, proxyPairs.First().Item2 as SkinnedMeshRenderer, _blendshapeCache);
|
||||
|
||||
return node.Refresh(proxyPairs, context, 0);
|
||||
var rv = await node.Refresh(proxyPairs, context, 0);
|
||||
if (rv == null)
|
||||
{
|
||||
context.Invalidate();
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private class Node : IRenderFilterNode
|
||||
|
@ -1,7 +1,7 @@
|
||||
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.UIElements"
|
||||
xmlns:ma="nadena.dev.modular_avatar.core.editor">
|
||||
<ui:VisualElement class="effect-group">
|
||||
<ed:ObjectField label="Reactive Component" name="effect__source"/>
|
||||
<ed:ObjectField label="ro_sim.effect_group.component" name="effect__source" class="ndmf-tr"/>
|
||||
|
||||
<ui:VisualElement name="effect__set-active" class="unity-base-field h-group">
|
||||
<ui:Label text="ro_sim.effect_group.controls_obj_state" class="unity-base-field__label ndmf-tr"/>
|
||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using nadena.dev.modular_avatar.ui;
|
||||
using nadena.dev.ndmf.localization;
|
||||
using nadena.dev.ndmf.preview;
|
||||
using nadena.dev.ndmf.preview.trace;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
@ -14,7 +15,8 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
{
|
||||
internal class ROSimulator : EditorWindow, IHasCustomMenu
|
||||
{
|
||||
public static PublishedValue<ImmutableDictionary<string, float>> PropertyOverrides = new(null);
|
||||
public static PublishedValue<ImmutableDictionary<string, float>> PropertyOverrides = new(null, debugName: "ROSimulator.PropertyOverrides");
|
||||
public static PublishedValue<ImmutableDictionary<string, ModularAvatarMenuItem>> MenuItemOverrides = new(null, debugName: "ROSimulator.MenuItemOverrides");
|
||||
|
||||
internal static string ROOT_PATH = "Packages/nadena.dev.modular-avatar/Editor/ReactiveObjects/Simulator/";
|
||||
private static string USS = ROOT_PATH + "ROSimulator.uss";
|
||||
@ -63,18 +65,39 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
PropertyOverrides.Value = ImmutableDictionary<string, float>.Empty;
|
||||
EditorApplication.delayCall += LoadUI;
|
||||
Selection.selectionChanged += SelectionChanged;
|
||||
is_enabled = true;
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
PropertyOverrides.Value = ImmutableDictionary<string, float>.Empty;
|
||||
MenuItemOverrides.Value = ImmutableDictionary<string, ModularAvatarMenuItem>.Empty;
|
||||
EditorApplication.delayCall += LoadUI;
|
||||
EditorApplication.update += PeriodicRefresh;
|
||||
Selection.selectionChanged += SelectionChanged;
|
||||
is_enabled = true;
|
||||
};
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Selection.selectionChanged -= SelectionChanged;
|
||||
is_enabled = false;
|
||||
|
||||
PropertyOverrides.Value = null;
|
||||
// Delay this to ensure that we don't try to change this value from within assembly reload callbacks
|
||||
// (which generates a noisy exception)
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
Selection.selectionChanged -= SelectionChanged;
|
||||
EditorApplication.update -= PeriodicRefresh;
|
||||
|
||||
PropertyOverrides.Value = null;
|
||||
MenuItemOverrides.Value = null;
|
||||
};
|
||||
}
|
||||
|
||||
private void PeriodicRefresh()
|
||||
{
|
||||
if (_refreshPending)
|
||||
{
|
||||
RefreshUI();
|
||||
}
|
||||
}
|
||||
|
||||
private ComputeContext _lastComputeContext;
|
||||
@ -85,8 +108,29 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
private Dictionary<(int, string), bool> foldoutState = new();
|
||||
private Button _btn_clear;
|
||||
|
||||
private bool _refreshPending;
|
||||
|
||||
private void RequestRefresh()
|
||||
{
|
||||
if (_refreshPending) return;
|
||||
|
||||
_refreshPending = true;
|
||||
|
||||
// For some reason, this seems to get dropped occasionally, resulting in us being wedged with _refreshPending = true.
|
||||
// Instead, we'll trigger this from EditorApplication.update...
|
||||
// EditorApplication.delayCall += RefreshUI;
|
||||
}
|
||||
|
||||
private void UpdatePropertyOverride(string prop, bool? enable, float f_val)
|
||||
{
|
||||
var trace = TraceBuffer.RecordTraceEvent(
|
||||
"ROSimulator.UpdatePropertyOverride",
|
||||
(ev) => $"Property {ev.Arg0} set to {ev.Arg1}",
|
||||
arg0: prop,
|
||||
arg1: enable == null ? "null" : enable.Value ? f_val : 0f
|
||||
);
|
||||
|
||||
using (trace.Scope())
|
||||
if (enable == null)
|
||||
{
|
||||
PropertyOverrides.Value = PropertyOverrides.Value.Remove(prop);
|
||||
@ -98,22 +142,36 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
{
|
||||
PropertyOverrides.Value = PropertyOverrides.Value.SetItem(prop, 0f);
|
||||
}
|
||||
|
||||
EditorApplication.delayCall += RefreshUI;
|
||||
|
||||
RequestRefresh();
|
||||
}
|
||||
|
||||
private void UpdatePropertyOverride(string prop, bool? value)
|
||||
|
||||
private void UpdateMenuItemOverride(string prop, ModularAvatarMenuItem item, bool? value)
|
||||
{
|
||||
var trace = TraceBuffer.RecordTraceEvent(
|
||||
"ROSimulator.UpdateMenuItemOverride",
|
||||
(ev) => $"MenuItem {ev.Arg0} for prop {ev.Arg1} set to {ev.Arg2}",
|
||||
arg0: item.gameObject.name,
|
||||
arg1: prop,
|
||||
arg2: value == null ? "null" : value.Value ? "true" : "false"
|
||||
);
|
||||
|
||||
using (trace.Scope())
|
||||
if (value == null)
|
||||
{
|
||||
PropertyOverrides.Value = PropertyOverrides.Value.Remove(prop);
|
||||
MenuItemOverrides.Value = MenuItemOverrides.Value.Remove(prop);
|
||||
}
|
||||
else if (value.Value)
|
||||
{
|
||||
MenuItemOverrides.Value = MenuItemOverrides.Value.SetItem(prop, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
PropertyOverrides.Value = PropertyOverrides.Value.SetItem(prop, value.Value ? 1f : 0f);
|
||||
if (!MenuItemOverrides.Value.TryGetValue(prop, out var existing) || ReferenceEquals(existing, item))
|
||||
MenuItemOverrides.Value = MenuItemOverrides.Value.SetItem(prop, null);
|
||||
}
|
||||
|
||||
RefreshUI();
|
||||
|
||||
RequestRefresh();
|
||||
}
|
||||
|
||||
private void ShowButton(Rect rect)
|
||||
@ -167,7 +225,8 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
_btn_clear.clickable.clicked += () =>
|
||||
{
|
||||
PropertyOverrides.Value = ImmutableDictionary<string, float>.Empty;
|
||||
RefreshUI();
|
||||
MenuItemOverrides.Value = ImmutableDictionary<string, ModularAvatarMenuItem>.Empty;
|
||||
RequestRefresh();
|
||||
};
|
||||
|
||||
e_debugInfo = root.Q<VisualElement>("debug-info");
|
||||
@ -182,11 +241,13 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
currentSelection = locked ? f_inspecting.value as GameObject : Selection.activeGameObject;
|
||||
f_inspecting.SetValueWithoutNotify(currentSelection);
|
||||
|
||||
RefreshUI();
|
||||
RequestRefresh();
|
||||
}
|
||||
|
||||
private void RefreshUI()
|
||||
{
|
||||
_refreshPending = false;
|
||||
|
||||
var avatar = RuntimeUtil.FindAvatarInParents(currentSelection?.transform);
|
||||
|
||||
if (avatar == null)
|
||||
@ -194,8 +255,8 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
e_debugInfo.style.display = DisplayStyle.None;
|
||||
return;
|
||||
}
|
||||
|
||||
_btn_clear.SetEnabled(!PropertyOverrides.Value.IsEmpty);
|
||||
|
||||
_btn_clear.SetEnabled(!PropertyOverrides.Value.IsEmpty || !MenuItemOverrides.Value.IsEmpty);
|
||||
|
||||
e_debugInfo.style.display = DisplayStyle.Flex;
|
||||
|
||||
@ -204,6 +265,7 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
|
||||
var analysis = new ReactiveObjectAnalyzer(_lastComputeContext);
|
||||
analysis.ForcePropertyOverrides = PropertyOverrides.Value;
|
||||
analysis.ForceMenuItems = MenuItemOverrides.Value;
|
||||
var result = analysis.Analyze(avatar.gameObject);
|
||||
|
||||
SetThisObjectOverrides(analysis);
|
||||
@ -215,18 +277,59 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
{
|
||||
if (self.is_enabled)
|
||||
{
|
||||
self.RefreshUI();
|
||||
self.RequestRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetThisObjectOverrides(ReactiveObjectAnalyzer analysis)
|
||||
{
|
||||
BindOverrideToParameter("this-obj-override", analysis.GetGameObjectStateProperty(currentSelection), 1);
|
||||
BindOverrideToParameter("this-menu-override", analysis.GetMenuItemProperty(currentSelection),
|
||||
currentSelection.GetComponent<ModularAvatarMenuItem>()?.Control?.value ?? 1
|
||||
);
|
||||
currentSelection.TryGetComponent<ModularAvatarMenuItem>(out var mami);
|
||||
BindOverrideToMenuItem("this-menu-override", mami);
|
||||
}
|
||||
|
||||
private string _menuItemOverrideProperty;
|
||||
private ModularAvatarMenuItem _menuItemOverrideTarget;
|
||||
|
||||
private void BindOverrideToMenuItem(string overrideElemName, ModularAvatarMenuItem mami)
|
||||
{
|
||||
var elem = e_debugInfo.Q<VisualElement>(overrideElemName);
|
||||
var soc = elem.Q<StateOverrideController>();
|
||||
|
||||
if (mami == null)
|
||||
{
|
||||
elem.style.display = DisplayStyle.None;
|
||||
return;
|
||||
}
|
||||
|
||||
var prop = ParameterAssignerPass.AssignMenuItemParameter(mami, forceSimulation: true)?.Parameter;
|
||||
if (prop == null)
|
||||
{
|
||||
elem.style.display = DisplayStyle.None;
|
||||
return;
|
||||
}
|
||||
|
||||
elem.style.display = DisplayStyle.Flex;
|
||||
|
||||
if (MenuItemOverrides.Value.TryGetValue(prop, out var overrideValue))
|
||||
soc.SetWithoutNotify(ReferenceEquals(mami, overrideValue));
|
||||
else
|
||||
soc.SetWithoutNotify(null);
|
||||
|
||||
// Avoid multiple registration of the same delegate here by reusing the same delegate instead of binding
|
||||
// these properties in a closure
|
||||
_menuItemOverrideProperty = prop;
|
||||
_menuItemOverrideTarget = mami;
|
||||
soc.OnStateOverrideChanged = MenuItemOverrideChanged;
|
||||
}
|
||||
|
||||
private void MenuItemOverrideChanged(bool? obj)
|
||||
{
|
||||
UpdateMenuItemOverride(_menuItemOverrideProperty, _menuItemOverrideTarget, obj);
|
||||
}
|
||||
|
||||
private string _propertyOverrideProperty;
|
||||
private float _propertyOverrideTargetValue;
|
||||
private void BindOverrideToParameter(string overrideElemName, string property, float targetValue)
|
||||
{
|
||||
var elem = e_debugInfo.Q<VisualElement>(overrideElemName);
|
||||
@ -247,11 +350,15 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
{
|
||||
soc.SetWithoutNotify(null);
|
||||
}
|
||||
|
||||
soc.OnStateOverrideChanged += value =>
|
||||
{
|
||||
UpdatePropertyOverride(property, value, targetValue);
|
||||
};
|
||||
|
||||
_propertyOverrideProperty = property;
|
||||
_propertyOverrideTargetValue = targetValue;
|
||||
soc.OnStateOverrideChanged = OnParameterOverrideChanged;
|
||||
}
|
||||
|
||||
private void OnParameterOverrideChanged(bool? state)
|
||||
{
|
||||
UpdatePropertyOverride(_propertyOverrideProperty, state, _propertyOverrideTargetValue);
|
||||
}
|
||||
|
||||
private void SetAffectedBy(GameObject gameObject, Dictionary<TargetProp, AnimatedProperty> shapes)
|
||||
@ -455,9 +562,24 @@ namespace nadena.dev.modular_avatar.core.editor.Simulator
|
||||
{
|
||||
targetValue = Mathf.Round((condition.ParameterValueLo + condition.ParameterValueHi) / 2);
|
||||
}
|
||||
|
||||
soc.OnStateOverrideChanged += value => UpdatePropertyOverride(prop, value, targetValue);
|
||||
|
||||
|
||||
if (condition.DebugReference is ModularAvatarMenuItem mami)
|
||||
{
|
||||
bool? menuOverride = null;
|
||||
|
||||
if (MenuItemOverrides.Value.TryGetValue(prop, out var target))
|
||||
{
|
||||
menuOverride = ReferenceEquals(mami, target);
|
||||
soc.SetWithoutNotify(menuOverride);
|
||||
}
|
||||
|
||||
soc.OnStateOverrideChanged = value => { UpdateMenuItemOverride(prop, mami, value); };
|
||||
}
|
||||
else
|
||||
{
|
||||
soc.OnStateOverrideChanged = value => UpdatePropertyOverride(prop, value, targetValue);
|
||||
}
|
||||
|
||||
var active = condition.InitiallyActive;
|
||||
var active_label = active ? "active" : "inactive";
|
||||
active_label = "ro_sim.state." + active_label;
|
||||
|
@ -16,7 +16,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
private static StyleSheet uss;
|
||||
private Button btn_disable, btn_default, btn_enable;
|
||||
public event System.Action<bool?> OnStateOverrideChanged;
|
||||
public System.Action<bool?> OnStateOverrideChanged;
|
||||
|
||||
public StateOverrideController()
|
||||
{
|
||||
|
@ -115,7 +115,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ResolvedParameter.saved |= info.ResolvedParameter.saved;
|
||||
ResolvedParameter.localOnly &= info.ResolvedParameter.localOnly;
|
||||
}
|
||||
|
||||
public void MergeChild(ParameterInfo info)
|
||||
@ -128,8 +129,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
ResolvedParameter.hasExplicitDefaultValue = info.ResolvedParameter.hasExplicitDefaultValue;
|
||||
ResolvedParameter.m_overrideAnimatorDefaults = info.ResolvedParameter.m_overrideAnimatorDefaults;
|
||||
}
|
||||
|
||||
ResolvedParameter.saved = info.ResolvedParameter.saved;
|
||||
}
|
||||
|
||||
void MergeCommon(ParameterInfo info)
|
||||
@ -154,8 +153,6 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
ConflictingValues = ConflictingValues.Union(info.ConflictingValues);
|
||||
ConflictingSyncTypes = ConflictingSyncTypes.Union(info.ConflictingSyncTypes);
|
||||
|
||||
ResolvedParameter.saved = ResolvedParameter.saved || info.ResolvedParameter.saved;
|
||||
|
||||
encounterOrder = Math.Min(encounterOrder, info.encounterOrder);
|
||||
}
|
||||
}
|
||||
@ -311,7 +308,7 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
newParameter.defaultValue = info.ResolvedParameter.HasDefaultValue ? info.ResolvedParameter.defaultValue : parameter.defaultValue;
|
||||
newParameter.name = parameter.name;
|
||||
newParameter.valueType = parameter.valueType;
|
||||
newParameter.networkSynced = parameter.networkSynced;
|
||||
newParameter.networkSynced = parameter.networkSynced || !info.ResolvedParameter.localOnly;
|
||||
newParameter.saved = parameter.saved || info.ResolvedParameter.saved;
|
||||
|
||||
return newParameter;
|
||||
|
@ -31,7 +31,7 @@ namespace nadena.dev.modular_avatar.core.editor.ScaleAdjuster
|
||||
public BoneState parentHint;
|
||||
}
|
||||
|
||||
private Dictionary<Component, BoneState> _bones = new(new ObjectIdentityComparer<Component>());
|
||||
private readonly Dictionary<Component, BoneState> _bones = new();
|
||||
//private List<BoneState> _states = new List<BoneState>();
|
||||
|
||||
public void Clear()
|
||||
|
@ -4,10 +4,14 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using nadena.dev.modular_avatar.core.editor.ScaleAdjuster;
|
||||
using nadena.dev.modular_avatar.core.armature_lock;
|
||||
using nadena.dev.ndmf.preview;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Jobs;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
#endregion
|
||||
|
||||
@ -54,117 +58,326 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
var scaleAdjusters = ctx.GetComponentsByType<ModularAvatarScaleAdjuster>();
|
||||
|
||||
var result = ImmutableList.CreateBuilder<RenderGroup>();
|
||||
var avatarToRenderer =
|
||||
new Dictionary<GameObject, HashSet<Renderer>>();
|
||||
|
||||
foreach (var adjuster in scaleAdjusters)
|
||||
foreach (var root in ctx.GetAvatarRoots())
|
||||
{
|
||||
if (adjuster == null) continue;
|
||||
if (ctx.GetComponentsInChildren<ModularAvatarScaleAdjuster>(root, true).Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find parent object
|
||||
// TODO: Reactive helper
|
||||
var root = FindAvatarRootObserving(ctx, adjuster.gameObject);
|
||||
if (root == null) continue;
|
||||
if (ctx.GetAvatarRoot(root?.transform?.parent?.gameObject) != null)
|
||||
{
|
||||
continue; // nested avatar descriptor
|
||||
}
|
||||
|
||||
var renderers = ctx.GetComponentsInChildren<Renderer>(root, true);
|
||||
var renderers = new HashSet<Renderer>();
|
||||
avatarToRenderer.Add(root, renderers);
|
||||
|
||||
foreach (var renderer in renderers)
|
||||
if (renderer is SkinnedMeshRenderer smr)
|
||||
result.Add(RenderGroup.For(renderer));
|
||||
foreach (var renderer in root.GetComponentsInChildren<Renderer>())
|
||||
{
|
||||
// For now, the preview system only supports MeshRenderer and SkinnedMeshRenderer
|
||||
if (renderer is not MeshRenderer and not SkinnedMeshRenderer) continue;
|
||||
|
||||
renderers.Add(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToImmutable();
|
||||
return avatarToRenderer.Select(kvp => RenderGroup.For(kvp.Value).WithData(kvp.Key)).ToImmutableList();
|
||||
}
|
||||
|
||||
public Task<IRenderFilterNode> Instantiate(RenderGroup group, IEnumerable<(Renderer, Renderer)> proxyPairs,
|
||||
ComputeContext context)
|
||||
{
|
||||
return new ScaleAdjusterPreviewNode().Refresh(proxyPairs, context, 0);
|
||||
return Task.FromResult<IRenderFilterNode>(new ScaleAdjusterPreviewNode(context, group, proxyPairs));
|
||||
}
|
||||
}
|
||||
|
||||
internal class ScaleAdjusterPreviewNode : IRenderFilterNode
|
||||
{
|
||||
private static ScaleAdjustedBones _bones = new ScaleAdjustedBones();
|
||||
private readonly HashSet<Transform> _knownProxies = new();
|
||||
|
||||
private readonly GameObject SourceAvatarRoot;
|
||||
private readonly GameObject VirtualAvatarRoot;
|
||||
|
||||
public ScaleAdjusterPreviewNode()
|
||||
private TransformAccessArray _srcBones;
|
||||
private TransformAccessArray _dstBones;
|
||||
|
||||
private NativeArray<bool> _boneIsValid;
|
||||
private NativeArray<TransformState> _boneStates;
|
||||
|
||||
// Map from bones found in initial proxy state to shadow bones
|
||||
private readonly Dictionary<Transform, Transform> _shadowBoneMap;
|
||||
|
||||
// Map from bones found in initial proxy state to shadow bones (with scale adjuster bones substituted)
|
||||
private readonly Dictionary<Transform, Transform> _finalBonesMap = new();
|
||||
|
||||
private readonly Dictionary<ModularAvatarScaleAdjuster, Transform> _scaleAdjusters =
|
||||
new();
|
||||
|
||||
private Dictionary<Renderer, Transform[]> _rendererBoneStates = new();
|
||||
|
||||
public ScaleAdjusterPreviewNode(ComputeContext context, RenderGroup group,
|
||||
IEnumerable<(Renderer, Renderer)> proxyPairs)
|
||||
{
|
||||
var proxyPairList = proxyPairs.ToList();
|
||||
|
||||
var avatarRoot = group.GetData<GameObject>();
|
||||
SourceAvatarRoot = avatarRoot;
|
||||
|
||||
var scene = NDMFPreviewSceneManager.GetPreviewScene();
|
||||
var priorScene = SceneManager.GetActiveScene();
|
||||
|
||||
var bonesSet = GetSourceBonesSet(context, proxyPairList);
|
||||
var bones = bonesSet.OrderBy(k => k.gameObject.name).ToArray();
|
||||
|
||||
Transform[] sourceBones;
|
||||
Transform[] destinationBones;
|
||||
try
|
||||
{
|
||||
SceneManager.SetActiveScene(scene);
|
||||
VirtualAvatarRoot = new GameObject(avatarRoot.name + " [ScaleAdjuster]");
|
||||
|
||||
_shadowBoneMap = CreateShadowBones(bones);
|
||||
sourceBones = new Transform[_shadowBoneMap.Count];
|
||||
destinationBones = new Transform[_shadowBoneMap.Count];
|
||||
|
||||
var i = 0;
|
||||
foreach (var (src, dst) in _shadowBoneMap)
|
||||
{
|
||||
sourceBones[i] = src;
|
||||
destinationBones[i] = dst;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
SceneManager.SetActiveScene(priorScene);
|
||||
}
|
||||
|
||||
_srcBones = new TransformAccessArray(sourceBones);
|
||||
_dstBones = new TransformAccessArray(destinationBones);
|
||||
|
||||
_boneIsValid = new NativeArray<bool>(sourceBones.Length, Allocator.Persistent);
|
||||
_boneStates = new NativeArray<TransformState>(sourceBones.Length, Allocator.Persistent);
|
||||
|
||||
FindScaleAdjusters(context);
|
||||
TransferBoneStates();
|
||||
}
|
||||
|
||||
public RenderAspects Reads => 0;
|
||||
|
||||
// We only change things in OnFrame, so downstream nodes will need to keep track of changes to these bones and
|
||||
// blendshapes themselves.
|
||||
public RenderAspects WhatChanged => 0;
|
||||
|
||||
private readonly Dictionary<Transform, ModularAvatarScaleAdjuster> _boneOverrides
|
||||
= new(new ObjectIdentityComparer<Transform>());
|
||||
|
||||
private Transform[] _boneArray, _newBoneArray;
|
||||
|
||||
public Task<IRenderFilterNode> Refresh
|
||||
(
|
||||
IEnumerable<(Renderer, Renderer)> proxyPairs,
|
||||
ComputeContext context,
|
||||
RenderAspects updatedAspects
|
||||
)
|
||||
private HashSet<Transform> GetSourceBonesSet(ComputeContext context, List<(Renderer, Renderer)> proxyPairs)
|
||||
{
|
||||
var pair = proxyPairs.First();
|
||||
Renderer original = pair.Item1;
|
||||
Renderer proxy = pair.Item2;
|
||||
|
||||
if (original != null && proxy != null && original is SkinnedMeshRenderer smr)
|
||||
var bonesSet = new HashSet<Transform>();
|
||||
foreach (var (_, r) in proxyPairs)
|
||||
{
|
||||
_boneOverrides.Clear();
|
||||
if (r == null) continue;
|
||||
|
||||
foreach (var bone in smr.bones)
|
||||
var rootBone = context.Observe(r, r_ => (r_ as SkinnedMeshRenderer)?.rootBone) ?? r.transform;
|
||||
bonesSet.Add(rootBone);
|
||||
|
||||
var smr = r as SkinnedMeshRenderer;
|
||||
if (smr == null) continue;
|
||||
|
||||
foreach (var b in context.Observe(smr, smr_ => smr_.bones, Enumerable.SequenceEqual))
|
||||
{
|
||||
var sa = bone?.GetComponent<ModularAvatarScaleAdjuster>();
|
||||
if (sa != null) {
|
||||
_boneOverrides.Add(bone, sa);
|
||||
if (b != null)
|
||||
{
|
||||
bonesSet.Add(b);
|
||||
}
|
||||
}
|
||||
|
||||
_boneArray = context.Observe(smr, s => s.bones, (b1, b2) =>
|
||||
{
|
||||
// SequenceEqual is quite slow due to having to go through Unity native calls for each object, use
|
||||
// reference equality instead
|
||||
if (b1.Length != b2.Length) return false;
|
||||
|
||||
for (var i = 0; i < b1.Length; i++)
|
||||
if (!ReferenceEquals(b1[i], b2[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
_newBoneArray = new Transform[_boneArray.Length];
|
||||
}
|
||||
|
||||
return Task.FromResult((IRenderFilterNode)this);
|
||||
|
||||
return bonesSet;
|
||||
}
|
||||
|
||||
private void FindScaleAdjusters(ComputeContext context)
|
||||
{
|
||||
_finalBonesMap.Clear();
|
||||
|
||||
foreach (var (sa, proxy) in _scaleAdjusters.ToList())
|
||||
{
|
||||
// Note: We leak the proxy here, as destroying it can cause visual artifacts. They'll eventually get
|
||||
// cleaned up whenever the pipeline is fully reset, or when the scene is reloaded.
|
||||
if (sa == null)
|
||||
{
|
||||
_scaleAdjusters.Remove(sa);
|
||||
}
|
||||
}
|
||||
|
||||
_scaleAdjusters.Clear();
|
||||
|
||||
foreach (var kvp in _shadowBoneMap) _finalBonesMap[kvp.Key] = kvp.Value;
|
||||
|
||||
foreach (var scaleAdjuster in context.GetComponentsInChildren<ModularAvatarScaleAdjuster>(
|
||||
SourceAvatarRoot.gameObject, true))
|
||||
{
|
||||
// If we don't find this in the map, we're not actually making use of this bone
|
||||
if (!_shadowBoneMap.TryGetValue(scaleAdjuster.transform, out var shadowBone)) continue;
|
||||
|
||||
var proxyShadow = new GameObject("[Scale Adjuster Proxy]");
|
||||
proxyShadow.transform.SetParent(shadowBone);
|
||||
proxyShadow.transform.localPosition = Vector3.zero;
|
||||
proxyShadow.transform.localRotation = Quaternion.identity;
|
||||
proxyShadow.transform.localScale = scaleAdjuster.Scale;
|
||||
|
||||
_scaleAdjusters[scaleAdjuster] = proxyShadow.transform;
|
||||
_finalBonesMap[scaleAdjuster.transform] = proxyShadow.transform;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IRenderFilterNode> Refresh(IEnumerable<(Renderer, Renderer)> proxyPairs, ComputeContext context,
|
||||
RenderAspects updatedAspects)
|
||||
{
|
||||
if (SourceAvatarRoot == null) return Task.FromResult<IRenderFilterNode>(null);
|
||||
|
||||
// Clean any destroyed objects out of _knownProxies to avoid growing this set indefinitely
|
||||
_knownProxies.RemoveWhere(p => p == null);
|
||||
|
||||
var proxyPairList = proxyPairs.ToList();
|
||||
|
||||
if (!GetSourceBonesSet(context, proxyPairList).SetEquals(_shadowBoneMap.Keys))
|
||||
return Task.FromResult<IRenderFilterNode>(null);
|
||||
|
||||
FindScaleAdjusters(context);
|
||||
TransferBoneStates();
|
||||
|
||||
return Task.FromResult<IRenderFilterNode>(this);
|
||||
}
|
||||
|
||||
private Dictionary<Transform, Transform> CreateShadowBones(Transform[] srcBones)
|
||||
{
|
||||
var srcToDst = new Dictionary<Transform, Transform>();
|
||||
|
||||
for (var i = 0; i < srcBones.Length; i++) GetShadowBone(srcBones[i]);
|
||||
|
||||
return srcToDst;
|
||||
|
||||
Transform GetShadowBone(Transform srcBone)
|
||||
{
|
||||
if (srcBone == null) return null;
|
||||
if (srcToDst.TryGetValue(srcBone, out var dstBone)) return dstBone;
|
||||
|
||||
var newBone = new GameObject(srcBone.name);
|
||||
newBone.transform.SetParent(GetShadowBone(srcBone.parent) ?? VirtualAvatarRoot.transform);
|
||||
newBone.transform.localPosition = srcBone.localPosition;
|
||||
newBone.transform.localRotation = srcBone.localRotation;
|
||||
newBone.transform.localScale = srcBone.localScale;
|
||||
|
||||
srcToDst[srcBone] = newBone.transform;
|
||||
|
||||
return newBone.transform;
|
||||
}
|
||||
}
|
||||
|
||||
private void TransferBoneStates()
|
||||
{
|
||||
var readTransforms = new ReadTransformsJob
|
||||
{
|
||||
BoneStates = _boneStates,
|
||||
BoneIsValid = _boneIsValid
|
||||
}.Schedule(_srcBones);
|
||||
|
||||
var writeTransforms = new WriteBoneStatesJob
|
||||
{
|
||||
BoneStates = _boneStates,
|
||||
BoneIsValid = _boneIsValid
|
||||
}.Schedule(_dstBones, readTransforms);
|
||||
|
||||
writeTransforms.Complete();
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct ReadTransformsJob : IJobParallelForTransform
|
||||
{
|
||||
[WriteOnly] public NativeArray<TransformState> BoneStates;
|
||||
[WriteOnly] public NativeArray<bool> BoneIsValid;
|
||||
|
||||
public void Execute(int index, TransformAccess transform)
|
||||
{
|
||||
BoneIsValid[index] = transform.isValid;
|
||||
|
||||
if (transform.isValid)
|
||||
{
|
||||
BoneStates[index] = new TransformState
|
||||
{
|
||||
localPosition = transform.position,
|
||||
localRotation = transform.rotation,
|
||||
localScale = transform.localScale
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct WriteBoneStatesJob : IJobParallelForTransform
|
||||
{
|
||||
[ReadOnly] public NativeArray<TransformState> BoneStates;
|
||||
[ReadOnly] public NativeArray<bool> BoneIsValid;
|
||||
|
||||
public void Execute(int index, TransformAccess transform)
|
||||
{
|
||||
if (BoneIsValid[index])
|
||||
{
|
||||
var state = BoneStates[index];
|
||||
transform.position = state.localPosition;
|
||||
transform.rotation = state.localRotation;
|
||||
transform.localScale = state.localScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RenderAspects WhatChanged => RenderAspects.Shapes;
|
||||
|
||||
public void OnFrameGroup()
|
||||
{
|
||||
TransferBoneStates();
|
||||
|
||||
foreach (var (sa, xform) in _scaleAdjusters)
|
||||
if (sa != null && xform != null)
|
||||
xform.localScale = sa.Scale;
|
||||
}
|
||||
|
||||
public void OnFrame(Renderer original, Renderer proxy)
|
||||
{
|
||||
if (_boneArray != null)
|
||||
if (proxy == null) return;
|
||||
|
||||
var curParent = proxy.transform.parent ?? original.transform.parent;
|
||||
if (_finalBonesMap.TryGetValue(curParent, out var newRoot))
|
||||
{
|
||||
for (int i = 0; i < _boneArray.Length; i++)
|
||||
{
|
||||
var b = _boneArray[i];
|
||||
|
||||
ModularAvatarScaleAdjuster sa = null;
|
||||
if (b != null) _boneOverrides.TryGetValue(b, out sa);
|
||||
|
||||
_newBoneArray[i] = _bones.GetBone(sa, true)?.proxy ?? b;
|
||||
}
|
||||
|
||||
((SkinnedMeshRenderer)proxy).bones = _newBoneArray;
|
||||
// We need to remember this proxy so we can avoid destroying it when we destroy VirtualAvatarRoot
|
||||
// in Dispose
|
||||
|
||||
_knownProxies.Add(proxy.transform);
|
||||
|
||||
proxy.transform.SetParent(newRoot, false);
|
||||
}
|
||||
|
||||
_bones.Update();
|
||||
}
|
||||
var smr = proxy as SkinnedMeshRenderer;
|
||||
if (smr == null) return;
|
||||
|
||||
var rootBone = _finalBonesMap.TryGetValue(smr.rootBone, out var newRootBone) ? newRootBone : smr.rootBone;
|
||||
smr.rootBone = rootBone;
|
||||
smr.bones = smr.bones.Select(b => b == null ? null : _finalBonesMap.GetValueOrDefault(b, b)).ToArray();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_bones.Clear();
|
||||
foreach (var proxy in _knownProxies)
|
||||
{
|
||||
if (proxy != null && proxy.IsChildOf(VirtualAvatarRoot.transform))
|
||||
{
|
||||
proxy.transform.SetParent(null, false);
|
||||
}
|
||||
}
|
||||
|
||||
Object.DestroyImmediate(VirtualAvatarRoot);
|
||||
|
||||
_srcBones.Dispose();
|
||||
_dstBones.Dispose();
|
||||
_boneIsValid.Dispose();
|
||||
_boneStates.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -385,7 +385,13 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
private static bool ValidateSetupOutfit(GameObject gameObj)
|
||||
{
|
||||
Object obj;
|
||||
if (gameObj == null)
|
||||
{
|
||||
errorHeader = S("setup_outfit.err.header.notarget");
|
||||
errorMessageGroups = new string[] { S("setup_outfit.err.no_selection") };
|
||||
return false;
|
||||
}
|
||||
|
||||
errorHeader = S_f("setup_outfit.err.header", gameObj.name);
|
||||
var xform = gameObj.transform;
|
||||
|
||||
@ -542,4 +548,4 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
return avatarHips != null && outfitHips != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,8 @@
|
||||
"VRM10",
|
||||
"nadena.dev.ndmf.reactive-query.core",
|
||||
"nadena.dev.ndmf.runtime",
|
||||
"VRC.SDK3A.Editor"
|
||||
"VRC.SDK3A.Editor",
|
||||
"Unity.Burst"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
|
@ -1,25 +1,35 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[Serializable]
|
||||
public class AvatarObjectReference
|
||||
{
|
||||
private static long HIERARCHY_CHANGED_SEQ = long.MinValue;
|
||||
private long ReferencesLockedAtFrame = long.MinValue;
|
||||
|
||||
public static string AVATAR_ROOT = "$$$AVATAR_ROOT$$$";
|
||||
public string referencePath;
|
||||
|
||||
[SerializeField] internal GameObject targetObject;
|
||||
|
||||
|
||||
private long _cacheSeq = long.MinValue;
|
||||
private bool _cacheValid;
|
||||
private string _cachedPath;
|
||||
private GameObject _cachedReference;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[InitializeOnLoadMethod]
|
||||
private static void Init()
|
||||
{
|
||||
EditorApplication.hierarchyChanged += () => HIERARCHY_CHANGED_SEQ += 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
public AvatarObjectReference Clone()
|
||||
{
|
||||
return new AvatarObjectReference
|
||||
@ -54,10 +64,12 @@ namespace nadena.dev.modular_avatar.core
|
||||
public GameObject Get(Component container)
|
||||
{
|
||||
bool cacheValid = _cacheValid || ReferencesLockedAtFrame == Time.frameCount;
|
||||
cacheValid &= HIERARCHY_CHANGED_SEQ == _cacheSeq;
|
||||
|
||||
if (cacheValid && _cachedPath == referencePath && _cachedReference != null) return _cachedReference;
|
||||
|
||||
_cacheValid = true;
|
||||
_cacheSeq = HIERARCHY_CHANGED_SEQ;
|
||||
_cachedPath = referencePath;
|
||||
|
||||
if (string.IsNullOrEmpty(referencePath))
|
||||
@ -66,9 +78,6 @@ namespace nadena.dev.modular_avatar.core
|
||||
return _cachedReference;
|
||||
}
|
||||
|
||||
RuntimeUtil.OnHierarchyChanged -= InvalidateCache;
|
||||
RuntimeUtil.OnHierarchyChanged += InvalidateCache;
|
||||
|
||||
var avatarTransform = RuntimeUtil.FindAvatarTransformInParents(container.transform);
|
||||
if (avatarTransform == null) return (_cachedReference = null);
|
||||
|
||||
@ -124,12 +133,12 @@ namespace nadena.dev.modular_avatar.core
|
||||
targetObject = target;
|
||||
}
|
||||
|
||||
private void InvalidateCache()
|
||||
internal bool IsConsistent(GameObject avatarRoot)
|
||||
{
|
||||
RuntimeUtil.OnHierarchyChanged -= InvalidateCache;
|
||||
_cacheValid = false;
|
||||
if (referencePath == AVATAR_ROOT) return targetObject == avatarRoot;
|
||||
return avatarRoot.transform.Find(referencePath)?.gameObject == targetObject;
|
||||
}
|
||||
|
||||
|
||||
protected bool Equals(AvatarObjectReference other)
|
||||
{
|
||||
return GetDirectTarget() == other.GetDirectTarget() && referencePath == other.referencePath;
|
||||
|
@ -24,7 +24,6 @@
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
using VRC.SDKBase;
|
||||
#endif
|
||||
|
9
Runtime/IHaveObjReferences.cs
Normal file
9
Runtime/IHaveObjReferences.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
internal interface IHaveObjReferences
|
||||
{
|
||||
IEnumerable<AvatarObjectReference> GetObjectReferences();
|
||||
}
|
||||
}
|
3
Runtime/IHaveObjReferences.cs.meta
Normal file
3
Runtime/IHaveObjReferences.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 979eb029e78d4916a24742853e8d7e53
|
||||
timeCreated: 1725229779
|
@ -36,7 +36,7 @@ namespace nadena.dev.modular_avatar.core
|
||||
[ExecuteAlways]
|
||||
[AddComponentMenu("Modular Avatar/MA Blendshape Sync")]
|
||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/blendshape-sync?lang=auto")]
|
||||
public class ModularAvatarBlendshapeSync : AvatarTagComponent
|
||||
public class ModularAvatarBlendshapeSync : AvatarTagComponent, IHaveObjReferences
|
||||
{
|
||||
public List<BlendshapeBinding> Bindings = new List<BlendshapeBinding>();
|
||||
|
||||
@ -72,7 +72,9 @@ namespace nadena.dev.modular_avatar.core
|
||||
|
||||
private void Rebind()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (this == null) return;
|
||||
if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this)) return;
|
||||
|
||||
_editorBindings = new List<EditorBlendshapeBinding>();
|
||||
|
||||
@ -110,6 +112,7 @@ namespace nadena.dev.modular_avatar.core
|
||||
}
|
||||
|
||||
Update();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Update()
|
||||
@ -126,5 +129,12 @@ namespace nadena.dev.modular_avatar.core
|
||||
localRenderer.SetBlendShapeWeight(binding.LocalBlendshapeIndex, weight);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<AvatarObjectReference> GetObjectReferences()
|
||||
{
|
||||
foreach (var binding in Bindings)
|
||||
if (binding.ReferenceMesh != null)
|
||||
yield return binding.ReferenceMesh;
|
||||
}
|
||||
}
|
||||
}
|
@ -205,6 +205,13 @@ namespace nadena.dev.modular_avatar.core
|
||||
|
||||
Transform iter = newTarget;
|
||||
|
||||
if (newTarget == null)
|
||||
{
|
||||
boneReference = HumanBodyBones.LastBone;
|
||||
subPath = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newTarget == avatarTransform)
|
||||
{
|
||||
boneReference = HumanBodyBones.LastBone;
|
||||
@ -230,4 +237,4 @@ namespace nadena.dev.modular_avatar.core
|
||||
_targetCache = newTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using nadena.dev.modular_avatar.core.menu;
|
||||
using UnityEngine;
|
||||
@ -43,7 +42,24 @@ namespace nadena.dev.modular_avatar.core
|
||||
/// </summary>
|
||||
public bool isDefault;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the value for this toggle or button menu item will be automatically selected.
|
||||
/// Typically, this will be zero for the default menu item, then subsequent menu items will be allocated
|
||||
/// sequentially in hierarchy order.
|
||||
/// </summary>
|
||||
public bool automaticValue;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
// Init settings only when added or reset manually from the Inspector.
|
||||
// Otherwise, some plugins that add this component may break in non-playmode builds.
|
||||
if (RuntimeUtil.IsResetFromInspector())
|
||||
{
|
||||
InitSettings();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InitSettings()
|
||||
{
|
||||
Control = new VRCExpressionsMenu.Control();
|
||||
Control.type = VRCExpressionsMenu.Control.ControlType.Toggle;
|
||||
@ -51,6 +67,7 @@ namespace nadena.dev.modular_avatar.core
|
||||
isSaved = true;
|
||||
isSynced = true;
|
||||
isDefault = false;
|
||||
automaticValue = true;
|
||||
|
||||
MenuSource = SubmenuSource.Children;
|
||||
}
|
||||
@ -132,6 +149,38 @@ namespace nadena.dev.modular_avatar.core
|
||||
if (control.subParameters.Length > maxSubParams)
|
||||
control.subParameters = control.subParameters.Take(maxSubParams).ToArray();
|
||||
}
|
||||
|
||||
internal VRCExpressionParameters.ValueType ExpressionParametersValueType
|
||||
{
|
||||
get
|
||||
{
|
||||
// 0, 1
|
||||
var type = VRCExpressionParameters.ValueType.Bool;
|
||||
|
||||
// 2, 3, ..., (255)
|
||||
if (Control.value > 1)
|
||||
{
|
||||
type = VRCExpressionParameters.ValueType.Int;
|
||||
}
|
||||
|
||||
// (-1.0), ..., -0.1, 0.1, ..., 0.9
|
||||
if (Control.value < 0 || Mathf.Abs(Control.value - Mathf.Round(Control.value)) > 0.01f)
|
||||
{
|
||||
type = VRCExpressionParameters.ValueType.Float;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
internal AnimatorControllerParameterType AnimatorControllerParameterType
|
||||
=> ExpressionParametersValueType switch
|
||||
{
|
||||
VRCExpressionParameters.ValueType.Bool => AnimatorControllerParameterType.Bool,
|
||||
VRCExpressionParameters.ValueType.Int => AnimatorControllerParameterType.Int,
|
||||
VRCExpressionParameters.ValueType.Float => AnimatorControllerParameterType.Float,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#if MA_VRCSDK3_AVATARS
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
|
||||
@ -51,6 +52,21 @@ namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
// Init settings only when added or reset manually from the Inspector.
|
||||
// Otherwise, some plugins that add this component may break in non-playmode builds.
|
||||
if (RuntimeUtil.IsResetFromInspector())
|
||||
{
|
||||
InitSettings();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InitSettings()
|
||||
{
|
||||
deleteAttachedAnimator = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ namespace nadena.dev.modular_avatar.core
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Modular Avatar/MA Merge Armature")]
|
||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/merge-armature?lang=auto")]
|
||||
public class ModularAvatarMergeArmature : AvatarTagComponent
|
||||
public class ModularAvatarMergeArmature : AvatarTagComponent, IHaveObjReferences
|
||||
{
|
||||
public AvatarObjectReference mergeTarget = new AvatarObjectReference();
|
||||
public GameObject mergeTargetObject => mergeTarget.Get(this);
|
||||
@ -236,5 +236,10 @@ namespace nadena.dev.modular_avatar.core
|
||||
RuntimeUtil.MarkDirty(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<AvatarObjectReference> GetObjectReferences()
|
||||
{
|
||||
if (mergeTarget != null) yield return mergeTarget;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
@ -6,7 +7,7 @@ namespace nadena.dev.modular_avatar.core
|
||||
[AddComponentMenu("Modular Avatar/MA Mesh Settings")]
|
||||
[DisallowMultipleComponent]
|
||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/mesh-settings?lang=auto")]
|
||||
public class ModularAvatarMeshSettings : AvatarTagComponent
|
||||
public class ModularAvatarMeshSettings : AvatarTagComponent, IHaveObjReferences
|
||||
{
|
||||
internal static readonly Bounds DEFAULT_BOUNDS = new Bounds(Vector3.zero, Vector3.one * 2);
|
||||
|
||||
@ -34,5 +35,11 @@ namespace nadena.dev.modular_avatar.core
|
||||
ProbeAnchor?.Get(this);
|
||||
RootBone?.Get(this);
|
||||
}
|
||||
|
||||
public IEnumerable<AvatarObjectReference> GetObjectReferences()
|
||||
{
|
||||
if (ProbeAnchor != null) yield return ProbeAnchor;
|
||||
if (RootBone != null) yield return RootBone;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
[AddComponentMenu("Modular Avatar/MA Replace Object")]
|
||||
[DisallowMultipleComponent]
|
||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/replace-object?lang=auto")]
|
||||
public class ModularAvatarReplaceObject : AvatarTagComponent
|
||||
public class ModularAvatarReplaceObject : AvatarTagComponent, IHaveObjReferences
|
||||
{
|
||||
public AvatarObjectReference targetObject = new AvatarObjectReference();
|
||||
|
||||
@ -13,5 +14,10 @@ namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
targetObject?.Get(this);
|
||||
}
|
||||
|
||||
public IEnumerable<AvatarObjectReference> GetObjectReferences()
|
||||
{
|
||||
if (targetObject != null) yield return targetObject;
|
||||
}
|
||||
}
|
||||
}
|
@ -39,8 +39,7 @@ namespace nadena.dev.modular_avatar.core
|
||||
|
||||
[AddComponentMenu("Modular Avatar/MA Material Setter")]
|
||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/material-setter?lang=auto")]
|
||||
|
||||
public class ModularAvatarMaterialSetter : ReactiveComponent
|
||||
public class ModularAvatarMaterialSetter : ReactiveComponent, IHaveObjReferences
|
||||
{
|
||||
[SerializeField] private List<MaterialSwitchObject> m_objects = new();
|
||||
|
||||
@ -57,5 +56,12 @@ namespace nadena.dev.modular_avatar.core
|
||||
obj.Object?.Get(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<AvatarObjectReference> GetObjectReferences()
|
||||
{
|
||||
foreach (var obj in m_objects)
|
||||
if (obj.Object != null)
|
||||
yield return obj.Object;
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ namespace nadena.dev.modular_avatar.core
|
||||
|
||||
[AddComponentMenu("Modular Avatar/MA Object Toggle")]
|
||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/object-toggle?lang=auto")]
|
||||
public class ModularAvatarObjectToggle : ReactiveComponent
|
||||
public class ModularAvatarObjectToggle : ReactiveComponent, IHaveObjReferences
|
||||
{
|
||||
[SerializeField] private List<ToggledObject> m_objects = new();
|
||||
|
||||
@ -39,5 +39,12 @@ namespace nadena.dev.modular_avatar.core
|
||||
obj.Object?.Get(this);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<AvatarObjectReference> GetObjectReferences()
|
||||
{
|
||||
foreach (var obj in m_objects)
|
||||
if (obj.Object != null)
|
||||
yield return obj.Object;
|
||||
}
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ namespace nadena.dev.modular_avatar.core
|
||||
|
||||
[AddComponentMenu("Modular Avatar/MA Shape Changer")]
|
||||
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/shape-changer?lang=auto")]
|
||||
public class ModularAvatarShapeChanger : ReactiveComponent
|
||||
public class ModularAvatarShapeChanger : ReactiveComponent, IHaveObjReferences
|
||||
{
|
||||
// Migration field to help with 1.10-beta series avatar data. Since this was never in a released version of MA,
|
||||
// this migration support will be removed in 1.10.0.
|
||||
@ -115,5 +115,12 @@ namespace nadena.dev.modular_avatar.core
|
||||
m_targetRenderer.targetObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<AvatarObjectReference> GetObjectReferences()
|
||||
{
|
||||
foreach (var shape in m_shapes)
|
||||
if (shape.Object != null)
|
||||
yield return shape.Object;
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
|
||||
@ -161,5 +161,32 @@ namespace nadena.dev.modular_avatar.core
|
||||
{
|
||||
OnHierarchyChanged?.Invoke();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static readonly Type addComponentWindowType = Type.GetType("UnityEditor.AddComponent.AddComponentWindow, UnityEditor");
|
||||
#endif
|
||||
|
||||
public static bool IsResetFromInspector(int skipStackFrames = 1)
|
||||
{
|
||||
#if !UNITY_EDITOR
|
||||
return false;
|
||||
#else
|
||||
var frames = new System.Diagnostics.StackTrace(skipStackFrames).GetFrames();
|
||||
|
||||
// Reset from component context menu
|
||||
if (frames.Length == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Added from Add Component button
|
||||
if (frames.Any(x => x.GetMethod().DeclaringType == addComponentWindowType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
#region
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace nadena.dev.modular_avatar
|
||||
{
|
||||
internal class ObjectIdentityComparer<T> : IEqualityComparer<T>
|
||||
{
|
||||
public bool Equals(T x, T y)
|
||||
{
|
||||
return (object)x == (object)y;
|
||||
}
|
||||
|
||||
public int GetHashCode(T obj)
|
||||
{
|
||||
if (obj == null) return 0;
|
||||
return RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e674cbd75db24fb2b238674cd7010edb
|
||||
timeCreated: 1709448428
|
8
UnitTests~/Animation/AvatarMask.meta
Normal file
8
UnitTests~/Animation/AvatarMask.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c2f284a674abf64385b28a69a56b1b7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
641
UnitTests~/Animation/AvatarMask/AvatarMaskRewriteTest.prefab
Normal file
641
UnitTests~/Animation/AvatarMask/AvatarMaskRewriteTest.prefab
Normal file
@ -0,0 +1,641 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &331101518860596682
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6576105763171871826}
|
||||
- component: {fileID: 6153053980314603517}
|
||||
- component: {fileID: 8060949548882505714}
|
||||
m_Layer: 0
|
||||
m_Name: anim-root
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &6576105763171871826
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 331101518860596682}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1051065088277815102}
|
||||
- {fileID: 7719834109032320146}
|
||||
m_Father: {fileID: 4630496911169132430}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &6153053980314603517
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 331101518860596682}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1bb122659f724ebf85fe095ac02dc339, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
animator: {fileID: 9100000, guid: 0ff8ee7fe6e04b148bcd1b0f04280efb, type: 2}
|
||||
layerType: 5
|
||||
deleteAttachedAnimator: 1
|
||||
pathMode: 0
|
||||
matchAvatarWriteDefaults: 0
|
||||
relativePathRoot:
|
||||
referencePath:
|
||||
targetObject: {fileID: 0}
|
||||
layerPriority: 0
|
||||
--- !u!95 &8060949548882505714
|
||||
Animator:
|
||||
serializedVersion: 5
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 331101518860596682}
|
||||
m_Enabled: 1
|
||||
m_Avatar: {fileID: 0}
|
||||
m_Controller: {fileID: 0}
|
||||
m_CullingMode: 0
|
||||
m_UpdateMode: 0
|
||||
m_ApplyRootMotion: 0
|
||||
m_LinearVelocityBlending: 0
|
||||
m_StabilizeFeet: 0
|
||||
m_WarningMessage:
|
||||
m_HasTransformHierarchy: 1
|
||||
m_AllowConstantClipSamplingOptimization: 1
|
||||
m_KeepAnimatorStateOnDisable: 0
|
||||
m_WriteDefaultValuesOnDisable: 0
|
||||
--- !u!1 &543650974643626063
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 140504302469088812}
|
||||
m_Layer: 0
|
||||
m_Name: UpperLeg.L
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &140504302469088812
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 543650974643626063}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 3047197517193367399}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &952105631822741082
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3047197517193367399}
|
||||
- component: {fileID: 9058074000880147469}
|
||||
m_Layer: 0
|
||||
m_Name: Hips
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &3047197517193367399
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 952105631822741082}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 140504302469088812}
|
||||
- {fileID: 6795937180053589467}
|
||||
m_Father: {fileID: 1051065088277815102}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &9058074000880147469
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 952105631822741082}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 42581d8044b64899834d3d515ab3a144, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
boneReference: 55
|
||||
subPath: parent/relocated-to
|
||||
attachmentMode: 1
|
||||
--- !u!1 &1739058348199845828
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1051065088277815102}
|
||||
m_Layer: 0
|
||||
m_Name: Armature
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1051065088277815102
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1739058348199845828}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 3047197517193367399}
|
||||
m_Father: {fileID: 6576105763171871826}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &3718079119007386003
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4148338894295080238}
|
||||
- component: {fileID: 2535491910780027538}
|
||||
- component: {fileID: 2858884839595194667}
|
||||
- component: {fileID: 694213692129131288}
|
||||
m_Layer: 0
|
||||
m_Name: AvatarMaskRewriteTest
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &4148338894295080238
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3718079119007386003}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: -0.28403878, y: 0.6166916, z: -0.25877237}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4630496911169132430}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!95 &2535491910780027538
|
||||
Animator:
|
||||
serializedVersion: 5
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3718079119007386003}
|
||||
m_Enabled: 1
|
||||
m_Avatar: {fileID: 0}
|
||||
m_Controller: {fileID: 0}
|
||||
m_CullingMode: 0
|
||||
m_UpdateMode: 0
|
||||
m_ApplyRootMotion: 0
|
||||
m_LinearVelocityBlending: 0
|
||||
m_StabilizeFeet: 0
|
||||
m_WarningMessage:
|
||||
m_HasTransformHierarchy: 1
|
||||
m_AllowConstantClipSamplingOptimization: 1
|
||||
m_KeepAnimatorStateOnDisable: 0
|
||||
m_WriteDefaultValuesOnDisable: 0
|
||||
--- !u!114 &2858884839595194667
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3718079119007386003}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 542108242, guid: 67cc4cb7839cd3741b63733d5adf0442, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
Name:
|
||||
ViewPosition: {x: 0, y: 1.6, z: 0.2}
|
||||
Animations: 0
|
||||
ScaleIPD: 1
|
||||
lipSync: 0
|
||||
lipSyncJawBone: {fileID: 0}
|
||||
lipSyncJawClosed: {x: 0, y: 0, z: 0, w: 1}
|
||||
lipSyncJawOpen: {x: 0, y: 0, z: 0, w: 1}
|
||||
VisemeSkinnedMesh: {fileID: 0}
|
||||
MouthOpenBlendShapeName: Facial_Blends.Jaw_Down
|
||||
VisemeBlendShapes: []
|
||||
unityVersion:
|
||||
portraitCameraPositionOffset: {x: 0, y: 0, z: 0}
|
||||
portraitCameraRotationOffset: {x: 0, y: 1, z: 0, w: -0.00000004371139}
|
||||
networkIDs: []
|
||||
customExpressions: 0
|
||||
expressionsMenu: {fileID: 0}
|
||||
expressionParameters: {fileID: 0}
|
||||
enableEyeLook: 0
|
||||
customEyeLookSettings:
|
||||
eyeMovement:
|
||||
confidence: 0.5
|
||||
excitement: 0.5
|
||||
leftEye: {fileID: 0}
|
||||
rightEye: {fileID: 0}
|
||||
eyesLookingStraight:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyesLookingUp:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyesLookingDown:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyesLookingLeft:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyesLookingRight:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidType: 0
|
||||
upperLeftEyelid: {fileID: 0}
|
||||
upperRightEyelid: {fileID: 0}
|
||||
lowerLeftEyelid: {fileID: 0}
|
||||
lowerRightEyelid: {fileID: 0}
|
||||
eyelidsDefault:
|
||||
upper:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
lower:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidsClosed:
|
||||
upper:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
lower:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidsLookingUp:
|
||||
upper:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
lower:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidsLookingDown:
|
||||
upper:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
lower:
|
||||
linked: 1
|
||||
left: {x: 0, y: 0, z: 0, w: 0}
|
||||
right: {x: 0, y: 0, z: 0, w: 0}
|
||||
eyelidsSkinnedMesh: {fileID: 0}
|
||||
eyelidsBlendshapes:
|
||||
customizeAnimationLayers: 1
|
||||
baseAnimationLayers:
|
||||
- isEnabled: 0
|
||||
type: 0
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 4
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 5
|
||||
animatorController: {fileID: 9100000, guid: ec60d09b25b60f94eb23ece9a8a4483e,
|
||||
type: 2}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 0
|
||||
specialAnimationLayers:
|
||||
- isEnabled: 0
|
||||
type: 6
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 7
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
- isEnabled: 0
|
||||
type: 8
|
||||
animatorController: {fileID: 0}
|
||||
mask: {fileID: 0}
|
||||
isDefault: 1
|
||||
AnimationPreset: {fileID: 0}
|
||||
animationHashSet: []
|
||||
autoFootsteps: 1
|
||||
autoLocomotion: 1
|
||||
collider_head:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_torso:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_footR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_footL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_handR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_handL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerIndexL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerMiddleL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerRingL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerLittleL:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerIndexR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerMiddleR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerRingR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
collider_fingerLittleR:
|
||||
isMirrored: 1
|
||||
state: 0
|
||||
transform: {fileID: 0}
|
||||
radius: 0
|
||||
height: 0
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
rotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
--- !u!114 &694213692129131288
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3718079119007386003}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: -1427037861, guid: 4ecd63eff847044b68db9453ce219299, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
launchedFromSDKPipeline: 0
|
||||
completedSDKPipeline: 0
|
||||
blueprintId:
|
||||
contentType: 0
|
||||
assetBundleUnityVersion:
|
||||
fallbackStatus: 0
|
||||
--- !u!1 &4741986706786512382
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6528027234780562867}
|
||||
m_Layer: 0
|
||||
m_Name: relocated-to
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &6528027234780562867
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4741986706786512382}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4630496911169132430}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &6166177242302574734
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7719834109032320146}
|
||||
m_Layer: 0
|
||||
m_Name: Body
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &7719834109032320146
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6166177242302574734}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 6576105763171871826}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &7750111654287810670
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4630496911169132430}
|
||||
m_Layer: 0
|
||||
m_Name: parent
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &4630496911169132430
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7750111654287810670}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 6576105763171871826}
|
||||
- {fileID: 6528027234780562867}
|
||||
m_Father: {fileID: 4148338894295080238}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &8268444398319358879
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6795937180053589467}
|
||||
m_Layer: 0
|
||||
m_Name: UpperLeg.R
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &6795937180053589467
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8268444398319358879}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 3047197517193367399}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 813e4c7531d0e5d49983b259e8a32e79
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
161
UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs
Normal file
161
UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs
Normal file
@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using nadena.dev.ndmf;
|
||||
using NUnit.Framework;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace modular_avatar_tests
|
||||
{
|
||||
public class AvatarMaskTest : TestBase
|
||||
{
|
||||
class ExtractedMask {
|
||||
public int[] humanoidMaskElements;
|
||||
public List<(string, float)> transformMaskElements;
|
||||
|
||||
public ExtractedMask(int[] humanoidMaskElements, List<(string, float)> transformMaskElements)
|
||||
{
|
||||
this.humanoidMaskElements = humanoidMaskElements;
|
||||
this.transformMaskElements = transformMaskElements;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == this) return true;
|
||||
if (!(obj is ExtractedMask other)) return false;
|
||||
|
||||
if (!humanoidMaskElements.SequenceEqual(other.humanoidMaskElements)) return false;
|
||||
return transformMaskElements.SequenceEqual(other.transformMaskElements);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(humanoidMaskElements, transformMaskElements);
|
||||
}
|
||||
|
||||
public static ExtractedMask FromAvatarMask(AvatarMask mask)
|
||||
{
|
||||
var so = new SerializedObject(mask);
|
||||
var m_Mask = so.FindProperty("m_Mask");
|
||||
var m_Elements = so.FindProperty("m_Elements");
|
||||
|
||||
var humanoidMaskElements = new int[m_Mask.arraySize];
|
||||
var maskElementCount = m_Mask.arraySize;
|
||||
for (var i = 0; i < maskElementCount; i++)
|
||||
{
|
||||
var element = m_Mask.GetArrayElementAtIndex(i);
|
||||
humanoidMaskElements[i] = element.intValue;
|
||||
}
|
||||
|
||||
var transformMaskElements = new List<(string, float)>();
|
||||
var transformElementCount = m_Elements.arraySize;
|
||||
for (var i = 0; i < transformElementCount; i++)
|
||||
{
|
||||
var element = m_Elements.GetArrayElementAtIndex(i);
|
||||
var path = element.FindPropertyRelative("m_Path").stringValue;
|
||||
var weight = element.FindPropertyRelative("m_Weight").floatValue;
|
||||
|
||||
transformMaskElements.Add((path, weight));
|
||||
}
|
||||
|
||||
return new ExtractedMask(humanoidMaskElements, transformMaskElements);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void whenAvatarMaskIsPresentOnMergedAnimator_originalMaskIsUnchanged()
|
||||
{
|
||||
var prefab = CreatePrefab("AvatarMaskRewriteTest.prefab");
|
||||
var originalMask = LoadAsset<AvatarMask>("test-mask.mask");
|
||||
|
||||
var originalState = ExtractedMask.FromAvatarMask(originalMask);
|
||||
|
||||
AvatarProcessor.ProcessAvatar(prefab);
|
||||
|
||||
var newMask = findFxLayer(prefab, "test").avatarMask;
|
||||
var baseFxMask = findFxLayer(prefab, "base-fx").avatarMask;
|
||||
Assert.AreNotEqual(originalMask, newMask);
|
||||
Assert.AreNotEqual(originalMask, baseFxMask);
|
||||
|
||||
var newState = ExtractedMask.FromAvatarMask(originalMask);
|
||||
|
||||
Assert.AreEqual(originalState, newState);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void whenAvatarMaskIsPresentOnMergedAnimator_rewritesPathsByPrefix()
|
||||
{
|
||||
var prefab = CreatePrefab("AvatarMaskRewriteTest.prefab");
|
||||
|
||||
AvatarProcessor.ProcessAvatar(prefab);
|
||||
|
||||
// Body -> parent/anim-root/Body
|
||||
|
||||
var newMask = findFxLayer(prefab, "test").avatarMask;
|
||||
var state = ExtractedMask.FromAvatarMask(newMask);
|
||||
|
||||
var parentIndex = state.transformMaskElements.FindIndex(e => e.Item1 == "parent");
|
||||
var animRootIndex = state.transformMaskElements.FindIndex(e => e.Item1 == "parent/anim-root");
|
||||
var bodyIndex = state.transformMaskElements.FindIndex(e => e.Item1 == "parent/anim-root/Body");
|
||||
|
||||
Assert.Greater(parentIndex, -1);
|
||||
Assert.Greater(animRootIndex, -1);
|
||||
Assert.Greater(bodyIndex, -1);
|
||||
|
||||
Assert.Greater(animRootIndex, parentIndex);
|
||||
Assert.Greater(bodyIndex, animRootIndex);
|
||||
|
||||
// Body is still enabled; the injected parent and parent/anim-root are not
|
||||
Assert.IsTrue(state.transformMaskElements[parentIndex].Item2 < 0.5f);
|
||||
Assert.IsTrue(state.transformMaskElements[animRootIndex].Item2 < 0.5f);
|
||||
Assert.IsTrue(state.transformMaskElements[bodyIndex].Item2 > 0.5f);
|
||||
|
||||
// Original paths are removed
|
||||
Assert.IsFalse(state.transformMaskElements.Any(e => e.Item1 == "Body"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void whenObjectMovedByBoneProxy_avatarMaskPathsAreRewritten()
|
||||
{
|
||||
var prefab = CreatePrefab("AvatarMaskRewriteTest.prefab");
|
||||
|
||||
AvatarProcessor.ProcessAvatar(prefab);
|
||||
|
||||
// Armature/Hips -> parent/relocated-to/Hips
|
||||
|
||||
var newMask = findFxLayer(prefab, "test").avatarMask;
|
||||
var state = ExtractedMask.FromAvatarMask(newMask);
|
||||
|
||||
var parentIndex = state.transformMaskElements.FindIndex(e => e.Item1 == "parent");
|
||||
var relocatedToIndex = state.transformMaskElements.FindIndex(e => e.Item1 == "parent/relocated-to");
|
||||
var hipsIndex = state.transformMaskElements.FindIndex(e => e.Item1 == "parent/relocated-to/Hips");
|
||||
// UpperLeg.L will be enabled, .R will be disabled
|
||||
var upperLegLIndex = state.transformMaskElements.FindIndex(e => e.Item1 == "parent/relocated-to/Hips/UpperLeg.L");
|
||||
var upperLegRIndex = state.transformMaskElements.FindIndex(e => e.Item1 == "parent/relocated-to/Hips/UpperLeg.R");
|
||||
|
||||
Assert.Greater(parentIndex, -1);
|
||||
Assert.Greater(relocatedToIndex, -1);
|
||||
Assert.Greater(hipsIndex, -1);
|
||||
Assert.Greater(upperLegLIndex, -1);
|
||||
Assert.Greater(upperLegRIndex, -1);
|
||||
|
||||
Assert.Greater(relocatedToIndex, parentIndex);
|
||||
Assert.Greater(hipsIndex, relocatedToIndex);
|
||||
Assert.Greater(upperLegLIndex, hipsIndex);
|
||||
Assert.Greater(upperLegRIndex, hipsIndex);
|
||||
|
||||
// Hips -> 1, .L -> 1, .R -> 0
|
||||
Assert.IsTrue(state.transformMaskElements[parentIndex].Item2 < 0.5f);
|
||||
Assert.IsTrue(state.transformMaskElements[relocatedToIndex].Item2 < 0.5f);
|
||||
Assert.IsTrue(state.transformMaskElements[hipsIndex].Item2 > 0.5f);
|
||||
Assert.IsTrue(state.transformMaskElements[upperLegLIndex].Item2 > 0.5f);
|
||||
Assert.IsTrue(state.transformMaskElements[upperLegRIndex].Item2 < 0.5f);
|
||||
|
||||
// Original paths are removed
|
||||
Assert.IsFalse(state.transformMaskElements.Any(e => e.Item1 == "Armature/Hips"));
|
||||
Assert.IsFalse(state.transformMaskElements.Any(e => e.Item1 == "Armature/Hips/UpperLeg.L"));
|
||||
Assert.IsFalse(state.transformMaskElements.Any(e => e.Item1 == "Armature/Hips/UpperLeg.R"));
|
||||
}
|
||||
}
|
||||
}
|
3
UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs.meta
Normal file
3
UnitTests~/Animation/AvatarMask/AvatarMaskTest.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb1e5c662bcc4bdba53cc5fd95c54c4a
|
||||
timeCreated: 1725399401
|
72
UnitTests~/Animation/AvatarMask/av-mask-test-ac.controller
Normal file
72
UnitTests~/Animation/AvatarMask/av-mask-test-ac.controller
Normal file
@ -0,0 +1,72 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: av-mask-test-ac
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters: []
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: test
|
||||
m_StateMachine: {fileID: 6076016822445767091}
|
||||
m_Mask: {fileID: 31900000, guid: ca0bdd9ea94848f40b9de82a5aefe3c1, type: 2}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
--- !u!1107 &6076016822445767091
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: test
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: 6835839612632277213}
|
||||
m_Position: {x: 105.600006, y: 210.40001, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: 6835839612632277213}
|
||||
--- !u!1102 &6835839612632277213
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: test
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: 0d8ccd650d65c9f4690d79c89246c906, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ff8ee7fe6e04b148bcd1b0f04280efb
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
72
UnitTests~/Animation/AvatarMask/base-fx-ac.controller
Normal file
72
UnitTests~/Animation/AvatarMask/base-fx-ac.controller
Normal file
@ -0,0 +1,72 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1107 &-9195093947163206206
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: base-fx
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: 801668394021468748}
|
||||
m_Position: {x: 163.09534, y: 390.7027, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: 801668394021468748}
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: base-fx-ac
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters: []
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: base-fx
|
||||
m_StateMachine: {fileID: -9195093947163206206}
|
||||
m_Mask: {fileID: 31900000, guid: ca0bdd9ea94848f40b9de82a5aefe3c1, type: 2}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
--- !u!1102 &801668394021468748
|
||||
AnimatorState:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 1
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: test
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: 0d8ccd650d65c9f4690d79c89246c906, type: 2}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec60d09b25b60f94eb23ece9a8a4483e
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 9100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
408
UnitTests~/Animation/AvatarMask/test-mask.mask
Normal file
408
UnitTests~/Animation/AvatarMask/test-mask.mask
Normal file
@ -0,0 +1,408 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!319 &31900000
|
||||
AvatarMask:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: test-mask
|
||||
m_Mask: 01000000000000000000000001000000010000000100000001000000010000000100000001000000010000000100000001000000
|
||||
m_Elements:
|
||||
- m_Path:
|
||||
m_Weight: 1
|
||||
- m_Path: Armature
|
||||
m_Weight: 0
|
||||
- m_Path: Armature/Hips
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_a.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_a.L/BackRibbon_a.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_a.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_a.R/BackRibbon_a.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_b.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_b.L/BackRibbon_b.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_b.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_b.R/BackRibbon_b.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_c.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_c.L/BackRibbon_c.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_c.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/BackRibbonRoot/BackRibbon_c.R/BackRibbon_c.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_a.001
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_a.001/Skirt_a.002
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_a.001/Skirt_a.002/Skirt_a.003
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_b.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_b.001.L/Skirt_b.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_b.001.L/Skirt_b.002.L/Skirt_b.003.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_b.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_b.001.R/Skirt_b.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_b.001.R/Skirt_b.002.R/Skirt_b.003.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_c.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_c.001.L/Skirt_c.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_c.001.L/Skirt_c.002.L/Skirt_c.003.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_c.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_c.001.R/Skirt_c.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_c.001.R/Skirt_c.002.R/Skirt_c.003.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_d.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_d.001.L/Skirt_d.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_d.001.L/Skirt_d.002.L/Skirt_d.003.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_d.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_d.001.R/Skirt_d.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_d.001.R/Skirt_d.002.R/Skirt_d.003.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_e.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_e.001.L/Skirt_e.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_e.001.L/Skirt_e.002.L/Skirt_e.003.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_e.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_e.001.R/Skirt_e.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_e.001.R/Skirt_e.002.R/Skirt_e.003.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_f.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_f.001.L/Skirt_f.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_f.001.L/Skirt_f.002.L/Skirt_f.003.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_f.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_f.001.R/Skirt_f.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_f.001.R/Skirt_f.002.R/Skirt_f.003.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_g.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_g.001.L/Skirt_g.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_g.001.L/Skirt_g.002.L/Skirt_g.003.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_g.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_g.001.R/Skirt_g.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_g.001.R/Skirt_g.002.R/Skirt_g.003.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_h.001
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_h.001/Skirt_h.002
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/SkirtRoot/Skirt_h.001/Skirt_h.002/Skirt_h.003
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/EarRoot
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/EarRoot/Ear.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/EarRoot/Ear.L/Ear.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/EarRoot/Ear.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/EarRoot/Ear.R/Ear.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.L/HairBack_b.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.L/HairBack_b.001.L/HairBack_b.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.L/HairBack_b.001.L/HairBack_b.002.L/HairBack_b.003.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.L/HairBack_b.001.L/HairBack_c.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.L/HairBack_b.001.L/HairBack_c.L/HairBack_c.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.L/HairBack_b.001.L/HairBack_d.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.R/HairBack_b.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.R/HairBack_b.001.R/HairBack_b.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.R/HairBack_b.001.R/HairBack_b.002.R/HairBack_b.003.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.R/HairBack_b.001.R/HairBack_c.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.R/HairBack_b.001.R/HairBack_c.R/HairBack_c.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_b.R/HairBack_b.001.R/HairBack_d.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_e.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_e.L/HairBack_e.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_e.L/HairBack_e.001.L/HairBack_e.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_e.L/HairBack_e.001.L/HairBack_e.002.L/HairBack_e.003.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_e.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_e.R/HairBack_e.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_e.R/HairBack_e.001.R/HairBack_e.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_e.R/HairBack_e.001.R/HairBack_e.002.R/HairBack_e.003.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.L/HairBack_z.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.L/HairBack_z.001.L/HairBack_z.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.L/HairBack_z.001.L/HairBack_z.002.L/HairBack_z.003.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.L/HairBack_z.004.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.R/HairBack_z.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.R/HairBack_z.001.R/HairBack_z.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.R/HairBack_z.001.R/HairBack_z.002.R/HairBack_z.003.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/HairBack_a/HairBack_a.001/HairBack_z.R/HairBack_z.004.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairfront.001
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairfront.001/Hairfront.002
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairfront1.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairfront1.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairfront2.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairfront2.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_a.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_a.L/Hairside_a.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_a.L/Hairside_a.001.L/Hairside_a.002.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_a.L/Hairside_b.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_a.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_a.R/Hairside_a.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_a.R/Hairside_a.001.R/Hairside_a.002.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_a.R/Hairside_b.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_c.001.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/HairRoot/Hairside_c.001.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/LeftEye
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Neck/Head/RightEye
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Index
|
||||
Proximal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Index
|
||||
Proximal.L/Index Intermediate.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Index
|
||||
Proximal.L/Index Intermediate.L/Index Distal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Little
|
||||
Proximal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Little
|
||||
Proximal.L/Little Intermediate.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Little
|
||||
Proximal.L/Little Intermediate.L/Little Distal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Middle
|
||||
Proximal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Middle
|
||||
Proximal.L/Middle Intermediate.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Middle
|
||||
Proximal.L/Middle Intermediate.L/Middle Distal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Ring
|
||||
Proximal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Ring
|
||||
Proximal.L/Ring Intermediate.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Ring
|
||||
Proximal.L/Ring Intermediate.L/Ring Distal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Thumb
|
||||
Proximal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Thumb
|
||||
Proximal.L/Thumb Intermediate.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.L/UpperArm.L/LowerArm.L/Hand.L/Thumb
|
||||
Proximal.L/Thumb Intermediate.L/Thumb Distal.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Index
|
||||
Proximal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Index
|
||||
Proximal.R/Index Intermediate.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Index
|
||||
Proximal.R/Index Intermediate.R/Index Distal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Little
|
||||
Proximal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Little
|
||||
Proximal.R/Little Intermediate.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Little
|
||||
Proximal.R/Little Intermediate.R/Little Distal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Little
|
||||
Proximal.R/Little Intermediate.R/Little Distal.R/Little Distal.R_end
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Middle
|
||||
Proximal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Middle
|
||||
Proximal.R/Middle Intermediate.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Middle
|
||||
Proximal.R/Middle Intermediate.R/Middle Distal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Ring
|
||||
Proximal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Ring
|
||||
Proximal.R/Ring Intermediate.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Ring
|
||||
Proximal.R/Ring Intermediate.R/Ring Distal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Thumb
|
||||
Proximal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Thumb
|
||||
Proximal.R/Thumb Intermediate.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Spine/Chest/Shoulder.R/UpperArm.R/LowerArm.R/Hand.R/Thumb
|
||||
Proximal.R/Thumb Intermediate.R/Thumb Distal.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/StrapRoot
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/StrapRoot/strap_a
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/StrapRoot/strap_a/strap_a.001
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/StrapRoot/strap_a/strap_a.001/strap_a.002
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/StrapRoot/strap_b
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/StrapRoot/strap_b/strap_b.001
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/StrapRoot/strap_b/strap_b.001/strap_b.002
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Tail
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Tail/Tail.001
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Tail/Tail.001/Tail.002
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Tail/Tail.001/Tail.002/Tail.003
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Tail/Tail.001/Tail.002/Tail.003/Tail.004
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Tail/Tail.001/Tail.002/Tail.003/Tail.004/Tail.005
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/Tail/Tail.001/Tail.002/Tail.003/Tail.004/Tail.005/Tail.006
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/UpperLeg.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/UpperLeg.L/LowerLeg.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/UpperLeg.L/LowerLeg.L/Foot.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/UpperLeg.L/LowerLeg.L/Foot.L/Toes.L
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/UpperLeg.R
|
||||
m_Weight: 0
|
||||
- m_Path: Armature/Hips/UpperLeg.R/LowerLeg.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/UpperLeg.R/LowerLeg.R/Foot.R
|
||||
m_Weight: 1
|
||||
- m_Path: Armature/Hips/UpperLeg.R/LowerLeg.R/Foot.R/Toes.R
|
||||
m_Weight: 1
|
||||
- m_Path: Body
|
||||
m_Weight: 1
|
8
UnitTests~/Animation/AvatarMask/test-mask.mask.meta
Normal file
8
UnitTests~/Animation/AvatarMask/test-mask.mask.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca0bdd9ea94848f40b9de82a5aefe3c1
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 31900000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
53
UnitTests~/Animation/AvatarMask/test.anim
Normal file
53
UnitTests~/Animation/AvatarMask/test.anim
Normal file
@ -0,0 +1,53 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!74 &7400000
|
||||
AnimationClip:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: test
|
||||
serializedVersion: 7
|
||||
m_Legacy: 0
|
||||
m_Compressed: 0
|
||||
m_UseHighQualityCurve: 1
|
||||
m_RotationCurves: []
|
||||
m_CompressedRotationCurves: []
|
||||
m_EulerCurves: []
|
||||
m_PositionCurves: []
|
||||
m_ScaleCurves: []
|
||||
m_FloatCurves: []
|
||||
m_PPtrCurves: []
|
||||
m_SampleRate: 60
|
||||
m_WrapMode: 0
|
||||
m_Bounds:
|
||||
m_Center: {x: 0, y: 0, z: 0}
|
||||
m_Extent: {x: 0, y: 0, z: 0}
|
||||
m_ClipBindingConstant:
|
||||
genericBindings: []
|
||||
pptrCurveMapping: []
|
||||
m_AnimationClipSettings:
|
||||
serializedVersion: 2
|
||||
m_AdditiveReferencePoseClip: {fileID: 0}
|
||||
m_AdditiveReferencePoseTime: 0
|
||||
m_StartTime: 0
|
||||
m_StopTime: 1
|
||||
m_OrientationOffsetY: 0
|
||||
m_Level: 0
|
||||
m_CycleOffset: 0
|
||||
m_HasAdditiveReferencePose: 0
|
||||
m_LoopTime: 0
|
||||
m_LoopBlend: 0
|
||||
m_LoopBlendOrientation: 0
|
||||
m_LoopBlendPositionY: 0
|
||||
m_LoopBlendPositionXZ: 0
|
||||
m_KeepOriginalOrientation: 0
|
||||
m_KeepOriginalPositionY: 1
|
||||
m_KeepOriginalPositionXZ: 0
|
||||
m_HeightFromFeet: 0
|
||||
m_Mirror: 0
|
||||
m_EditorCurves: []
|
||||
m_EulerEditorCurves: []
|
||||
m_HasGenericRootTransform: 0
|
||||
m_HasMotionFloatCurves: 0
|
||||
m_Events: []
|
8
UnitTests~/Animation/AvatarMask/test.anim.meta
Normal file
8
UnitTests~/Animation/AvatarMask/test.anim.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d8ccd650d65c9f4690d79c89246c906
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
3
UnitTests~/PropCacheTest.meta
Normal file
3
UnitTests~/PropCacheTest.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77aba4362cec4cea91ee64c6e640b6b2
|
||||
timeCreated: 1726436552
|
71
UnitTests~/PropCacheTest/PropCacheTest.cs
Normal file
71
UnitTests~/PropCacheTest/PropCacheTest.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using nadena.dev.modular_avatar.core.editor;
|
||||
using nadena.dev.ndmf.preview;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnitTests.PropCacheTest
|
||||
{
|
||||
public class PropCacheTest
|
||||
{
|
||||
[UnityTest]
|
||||
public IEnumerator TestCacheInvalidation()
|
||||
{
|
||||
int seq = 0;
|
||||
|
||||
Dictionary<int, List<WeakReference<ComputeContext>>> invalidators = new();
|
||||
PropCache<int,int> cache = new PropCache<int, int>("test", (ctx, k) =>
|
||||
{
|
||||
Debug.Log("Generating value for " + k);
|
||||
if (!invalidators.TryGetValue(k, out var list))
|
||||
{
|
||||
list = new List<WeakReference<ComputeContext>>();
|
||||
invalidators[k] = list;
|
||||
}
|
||||
|
||||
list.Add(new WeakReference<ComputeContext>(ctx));
|
||||
|
||||
return (k * 10) + seq++;
|
||||
});
|
||||
|
||||
ComputeContext ctx = new ComputeContext("c1");
|
||||
int val = cache.Get(ctx, 1);
|
||||
Assert.AreEqual(10, val);
|
||||
|
||||
ComputeContext ctx2 = new ComputeContext("c2");
|
||||
val = cache.Get(ctx2, 1);
|
||||
Assert.AreEqual(10, val);
|
||||
|
||||
invalidators[1][0].TryGetTarget(out var target);
|
||||
target?.Invalidate();
|
||||
|
||||
Debug.Log("Pre-flush");
|
||||
ComputeContext.FlushInvalidates();
|
||||
Debug.Log("Mid-flush");
|
||||
ComputeContext.FlushInvalidates();
|
||||
Debug.Log("Post-flush");
|
||||
|
||||
// Task processing can happen asynchronously.
|
||||
|
||||
int limit = 10;
|
||||
while (limit-- > 0 && (!ctx.IsInvalidated || !ctx2.IsInvalidated))
|
||||
{
|
||||
Debug.Log("Waiting for invalidation: " + limit);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
Assert.IsTrue(ctx.IsInvalidated);
|
||||
Assert.IsTrue(ctx2.IsInvalidated);
|
||||
|
||||
val = cache.Get(ctx, 1);
|
||||
Assert.AreEqual(12, val);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
3
UnitTests~/PropCacheTest/PropCacheTest.cs.meta
Normal file
3
UnitTests~/PropCacheTest/PropCacheTest.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6891e8d136c942878bca9b50b5c58ec9
|
||||
timeCreated: 1726436558
|
3
UnitTests~/ReactiveComponent.meta
Normal file
3
UnitTests~/ReactiveComponent.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 669cbbad6e83460b9ca556b3eb09892c
|
||||
timeCreated: 1726276590
|
3
UnitTests~/ReactiveComponent/ParameterAssignment.meta
Normal file
3
UnitTests~/ReactiveComponent/ParameterAssignment.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c5ab15efda74442a395eaff69d63374
|
||||
timeCreated: 1726276597
|
@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using modular_avatar_tests;
|
||||
using nadena.dev.modular_avatar.core;
|
||||
using nadena.dev.modular_avatar.core.editor;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
|
||||
namespace UnitTests.ReactiveComponent.ParameterAssignment
|
||||
{
|
||||
public class AutoValueAssignmentTests : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void ManuallyAssignedParametersAreNotReplaced()
|
||||
{
|
||||
TestAssignments(new[] { (false, 1.0f) }, new[] { 1.0f });
|
||||
TestAssignments(new[] { (false, 1.0f) }, new[] { 1.0f }, 0);
|
||||
|
||||
TestAssignments(new[] { (false, 4.0f) }, new[] { 4.0f });
|
||||
TestAssignments(new[] { (false, 4.0f) }, new[] { 4.0f }, 0);
|
||||
|
||||
TestAssignments(new[] { (false, 1.0f), (false, 4.0f) }, new[] { 1.0f, 4.0f });
|
||||
TestAssignments(new[] { (false, 1.0f), (false, 4.0f) }, new[] { 1.0f, 4.0f }, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SingleEntryToggles()
|
||||
{
|
||||
TestAssignments(new[] { (true, 0.0f) }, new[] { 1.0f });
|
||||
TestAssignments(new[] { (true, 0.0f) }, new[] { 1.0f }, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultiEntryToggles()
|
||||
{
|
||||
TestAssignments(new[] { (true, 0.0f), (true, 0.0f) }, new[] { 0.0f, 1.0f }, 0);
|
||||
TestAssignments(new[] { (true, 0.0f), (true, 0.0f) }, new[] { 1.0f, 0.0f }, 1);
|
||||
TestAssignments(new[] { (true, 0.0f), (true, 0.0f) }, new[] { 1.0f, 2.0f }, null);
|
||||
|
||||
TestAssignments(new[] { (true, 0.0f), (true, 0.0f), (true, 0.0f) }, new[] { 1.0f, 0.0f, 2.0f }, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MixedAutoTests()
|
||||
{
|
||||
TestAssignments(new[] { (false, 2.0f), (true, 0.0f), (true, 0.0f) }, new[] { 2.0f, 1.0f, 3.0f }, null);
|
||||
TestAssignments(new[] { (false, 2.7f), (true, 0.0f), (true, 0.0f) }, new[] { 2.7f, 1.0f, 3.0f }, null);
|
||||
TestAssignments(new[] { (true, 1.0f), (false, 0.0f) }, new[] { 2.0f, 0.0f }, null, overrideExpectedDefaultValue: 1.0f);
|
||||
TestAssignments(new[] { (true, 1.0f), (false, 0.0f) }, new[] { 1.0f, 0.0f }, 0);
|
||||
TestAssignments(new[] { (true, 1.0f), (false, 0.0f) }, new[] { 1.0f, 0.0f }, 1);
|
||||
|
||||
}
|
||||
|
||||
void TestAssignments(
|
||||
(bool, float)[] assignments,
|
||||
float[] expectedAssignments,
|
||||
int? defaultIndex = null,
|
||||
Action<List<ModularAvatarMenuItem>> customize = null,
|
||||
float? overrideExpectedDefaultValue = null
|
||||
)
|
||||
{
|
||||
var root = CreateRoot("root");
|
||||
|
||||
var avDesc = root.GetComponent<VRCAvatarDescriptor>();
|
||||
avDesc.expressionParameters = ScriptableObject.CreateInstance<VRCExpressionParameters>();
|
||||
avDesc.expressionParameters.parameters = Array.Empty<VRCExpressionParameters.Parameter>();
|
||||
|
||||
List<ModularAvatarMenuItem> menuItems = new();
|
||||
|
||||
foreach (var (auto, value) in assignments)
|
||||
{
|
||||
var obj = CreateChild(root, "m" + (menuItems.Count));
|
||||
var mami = obj.AddComponent<ModularAvatarMenuItem>();
|
||||
|
||||
mami.Control = new VRCExpressionsMenu.Control()
|
||||
{
|
||||
name = obj.name,
|
||||
type = VRCExpressionsMenu.Control.ControlType.Toggle,
|
||||
value = value,
|
||||
parameter = new()
|
||||
{
|
||||
name = "test_parameter"
|
||||
}
|
||||
};
|
||||
|
||||
mami.automaticValue = auto;
|
||||
mami.isDefault = false;
|
||||
|
||||
menuItems.Add(mami);
|
||||
}
|
||||
|
||||
if (defaultIndex.HasValue)
|
||||
{
|
||||
menuItems[defaultIndex.Value].isDefault = true;
|
||||
}
|
||||
|
||||
customize?.Invoke(menuItems);
|
||||
|
||||
var context = new nadena.dev.ndmf.BuildContext(root, null);
|
||||
|
||||
new ParameterAssignerPass().TestExecute(context);
|
||||
|
||||
foreach (var (mami, expected) in menuItems.Zip(expectedAssignments, (m, e) => (m, e)))
|
||||
{
|
||||
Assert.AreEqual(expected, mami.Control.value);
|
||||
}
|
||||
|
||||
var expectedDefaultValue = overrideExpectedDefaultValue ?? (defaultIndex.HasValue ? expectedAssignments[defaultIndex.Value] : 0);
|
||||
Assert.AreEqual(expectedDefaultValue, avDesc.expressionParameters.parameters.Single().defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bd4c63c1feb4efa91a2ee4c53e606d3
|
||||
timeCreated: 1726276608
|
@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using modular_avatar_tests;
|
||||
using nadena.dev.modular_avatar.core;
|
||||
using nadena.dev.modular_avatar.core.editor;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Avatars.Components;
|
||||
using VRC.SDK3.Avatars.ScriptableObjects;
|
||||
|
||||
namespace UnitTests.ReactiveComponent.ParameterAssignment
|
||||
{
|
||||
public class ParameterTypeTests : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void BoolTest()
|
||||
{
|
||||
Test(new[] { 0.0f }, VRCExpressionParameters.ValueType.Bool);
|
||||
Test(new[] { 1.0f }, VRCExpressionParameters.ValueType.Bool);
|
||||
|
||||
Test(new[] { 0.0f, 1.0f }, VRCExpressionParameters.ValueType.Bool);
|
||||
Test(new[] { 1.0f, 0.0f }, VRCExpressionParameters.ValueType.Bool);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IntTest()
|
||||
{
|
||||
Test(new[] { 2.0f }, VRCExpressionParameters.ValueType.Int);
|
||||
Test(new[] { 3.0f }, VRCExpressionParameters.ValueType.Int);
|
||||
|
||||
Test(new[] { 0.0f, 1.0f, 2.0f }, VRCExpressionParameters.ValueType.Int);
|
||||
Test(new[] { 2.0f, 1.0f, 0.0f }, VRCExpressionParameters.ValueType.Int);
|
||||
|
||||
Test(new[] { 253.0f, 254.0f, 255.0f }, VRCExpressionParameters.ValueType.Int);
|
||||
Test(new[] { 255.0f, 254.0f, 253.0f }, VRCExpressionParameters.ValueType.Int);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FloatTest()
|
||||
{
|
||||
Test(new[] { -1.0f }, VRCExpressionParameters.ValueType.Float);
|
||||
Test(new[] { -0.1f }, VRCExpressionParameters.ValueType.Float);
|
||||
Test(new[] { 0.1f }, VRCExpressionParameters.ValueType.Float);
|
||||
Test(new[] { 0.9f }, VRCExpressionParameters.ValueType.Float);
|
||||
|
||||
Test(new[] { -1.0f, 0.0f, 1.0f }, VRCExpressionParameters.ValueType.Float);
|
||||
Test(new[] { 1.0f, 0.0f, -1.0f }, VRCExpressionParameters.ValueType.Float);
|
||||
|
||||
Test(new[] { -0.1f, 0.0f, 0.1f }, VRCExpressionParameters.ValueType.Float);
|
||||
Test(new[] { 0.1f, 0.0f, -0.1f }, VRCExpressionParameters.ValueType.Float);
|
||||
|
||||
Test(new[] { 0.0f, 0.1f, 0.2f }, VRCExpressionParameters.ValueType.Float);
|
||||
Test(new[] { 0.2f, 0.1f, 0.0f }, VRCExpressionParameters.ValueType.Float);
|
||||
|
||||
Test(new[] { 0.7f, 0.8f, 0.9f }, VRCExpressionParameters.ValueType.Float);
|
||||
Test(new[] { 0.9f, 0.8f, 0.7f }, VRCExpressionParameters.ValueType.Float);
|
||||
}
|
||||
|
||||
private void Test(float[] values, VRCExpressionParameters.ValueType expected)
|
||||
{
|
||||
var root = CreateRoot("Root");
|
||||
var descriptor = root.GetComponent<VRCAvatarDescriptor>();
|
||||
descriptor.expressionParameters = ScriptableObject.CreateInstance<VRCExpressionParameters>();
|
||||
descriptor.expressionParameters.parameters = Array.Empty<VRCExpressionParameters.Parameter>();
|
||||
|
||||
for (var i = 0; i < values.Length; i++)
|
||||
{
|
||||
var obj = CreateChild(root, i.ToString());
|
||||
var mami = obj.AddComponent<ModularAvatarMenuItem>();
|
||||
mami.Control = new VRCExpressionsMenu.Control()
|
||||
{
|
||||
name = obj.name,
|
||||
type = VRCExpressionsMenu.Control.ControlType.Toggle,
|
||||
value = values[i],
|
||||
parameter = new() { name = "Test" },
|
||||
};
|
||||
}
|
||||
|
||||
var context = new nadena.dev.ndmf.BuildContext(root, null);
|
||||
new ParameterAssignerPass().TestExecute(context);
|
||||
|
||||
Assert.AreEqual(expected, descriptor.expressionParameters.parameters.Single().valueType);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7051ac5a4bbe084c9d34c01c523eda3
|
||||
timeCreated: 1726276608
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user