Merge branch 'main' into vrm

# Conflicts:
#	Editor/nadena.dev.modular-avatar.core.editor.asmdef
This commit is contained in:
kaikoga 2024-09-29 18:30:37 +09:00
commit 88ee50977b
107 changed files with 3864 additions and 1019 deletions

View File

@ -19,7 +19,7 @@
"dependencies": {}
},
"nadena.dev.ndmf": {
"version": "1.5.0-rc.3"
"version": "1.5.0-rc.11"
}
}
}

View File

@ -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 }}

View File

@ -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();
}
}
}

View File

@ -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)

View File

@ -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();

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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"},
};

View File

@ -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
}
}
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;

View File

@ -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()

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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)

View File

@ -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>();

View File

@ -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",

View File

@ -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)
);

View File

@ -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");
}
}
}
}

View File

@ -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);
}

View File

@ -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>

View File

@ -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);
}
}
}
}
}

View File

@ -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 =>

View File

@ -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>

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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()

View File

@ -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;
}

View File

@ -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"
}
}

View File

@ -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": "このコンポーネントは頭ボーンの配下でないと効果がありません。\nBone 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": "条件"
}
}

View File

@ -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": "이 아바타안의 다른 오브젝트들"
}
}

View File

@ -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)"
}
}

View File

@ -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": "條件"
}

View File

@ -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)
{

View File

@ -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;

View 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);
}
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eba8a4ec992b42d894f9206c642c49ad
timeCreated: 1725229885

View File

@ -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)

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -109,7 +109,7 @@ namespace nadena.dev.modular_avatar.core.editor
{
return uxml != null && EditorStyles.label != null;
}
catch (NullReferenceException _)
catch (NullReferenceException)
{
return false;
}

View File

@ -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;
}
}
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}
}
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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,
};
}

View File

@ -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++)
{

View File

@ -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

View File

@ -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"/>

View File

@ -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;

View File

@ -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()
{

View File

@ -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;

View File

@ -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()

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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"

View File

@ -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;

View File

@ -24,7 +24,6 @@
using System;
using UnityEngine;
#if MA_VRCSDK3_AVATARS
using VRC.SDKBase;
#endif

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace nadena.dev.modular_avatar.core
{
internal interface IHaveObjReferences
{
IEnumerable<AvatarObjectReference> GetObjectReferences();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 979eb029e78d4916a24742853e8d7e53
timeCreated: 1725229779

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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,
};
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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
}
}
}

View File

@ -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);
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: e674cbd75db24fb2b238674cd7010edb
timeCreated: 1709448428

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5c2f284a674abf64385b28a69a56b1b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 813e4c7531d0e5d49983b259e8a32e79
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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"));
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eb1e5c662bcc4bdba53cc5fd95c54c4a
timeCreated: 1725399401

View 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:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0ff8ee7fe6e04b148bcd1b0f04280efb
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View 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:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ec60d09b25b60f94eb23ece9a8a4483e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View 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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ca0bdd9ea94848f40b9de82a5aefe3c1
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 31900000
userData:
assetBundleName:
assetBundleVariant:

View 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: []

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0d8ccd650d65c9f4690d79c89246c906
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 77aba4362cec4cea91ee64c6e640b6b2
timeCreated: 1726436552

View 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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6891e8d136c942878bca9b50b5c58ec9
timeCreated: 1726436558

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 669cbbad6e83460b9ca556b3eb09892c
timeCreated: 1726276590

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5c5ab15efda74442a395eaff69d63374
timeCreated: 1726276597

View File

@ -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);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3bd4c63c1feb4efa91a2ee4c53e606d3
timeCreated: 1726276608

View File

@ -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);
}
}
}

View File

@ -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