mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-01 20:25:07 +08:00
feat: add a component to perform nonuniform scale adjustments (#583)
This commit is contained in:
parent
4d3f49306e
commit
2650566f9a
148
Editor/Inspector/ScaleAdjusterInspector.cs
Normal file
148
Editor/Inspector/ScaleAdjusterInspector.cs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using static nadena.dev.modular_avatar.core.editor.Localization;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.core.editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(ModularAvatarScaleAdjuster))]
|
||||||
|
[CanEditMultipleObjects]
|
||||||
|
internal class ScaleAdjusterInspector : MAEditorBase
|
||||||
|
{
|
||||||
|
private SerializedProperty _scale;
|
||||||
|
|
||||||
|
private ModularAvatarScaleAdjuster[] _sortedTargets;
|
||||||
|
private Vector3[] _originalScales;
|
||||||
|
|
||||||
|
private Vector3 gizmoScale = Vector3.one;
|
||||||
|
|
||||||
|
private bool _adjustChildPositions;
|
||||||
|
|
||||||
|
protected void OnEnable()
|
||||||
|
{
|
||||||
|
_scale = serializedObject.FindProperty("m_Scale");
|
||||||
|
|
||||||
|
_sortedTargets = targets.Cast<ModularAvatarScaleAdjuster>().OrderBy(TransformDepth).ToArray();
|
||||||
|
_originalScales = _sortedTargets.Select(t => t.Scale).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int TransformDepth(ModularAvatarScaleAdjuster obj)
|
||||||
|
{
|
||||||
|
var t = obj.transform;
|
||||||
|
var depth = 0;
|
||||||
|
|
||||||
|
while (t != null)
|
||||||
|
{
|
||||||
|
depth++;
|
||||||
|
t = t.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnDisable()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSceneGUI()
|
||||||
|
{
|
||||||
|
Selection.selectionChanged -= UnhideTools;
|
||||||
|
Selection.selectionChanged += UnhideTools;
|
||||||
|
Tools.hidden = (Tools.current == Tool.Scale);
|
||||||
|
if (!Tools.hidden) return;
|
||||||
|
|
||||||
|
var handlePos = _sortedTargets[0].transform;
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
var handleSize = HandleUtility.GetHandleSize(handlePos.position);
|
||||||
|
gizmoScale = Handles.ScaleHandle(gizmoScale, handlePos.position, handlePos.rotation, handleSize);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _sortedTargets.Length; i++)
|
||||||
|
{
|
||||||
|
UpdateScale(i, handlePos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateScale(int i, Transform refTransform)
|
||||||
|
{
|
||||||
|
var xform = _sortedTargets[i].transform;
|
||||||
|
var target = _sortedTargets[i];
|
||||||
|
|
||||||
|
Matrix4x4 initialTransform = xform.parent.localToWorldMatrix * Matrix4x4.TRS(
|
||||||
|
xform.localPosition,
|
||||||
|
xform.localRotation,
|
||||||
|
xform.localScale
|
||||||
|
);
|
||||||
|
|
||||||
|
Matrix4x4 initialScale = Matrix4x4.TRS(
|
||||||
|
Vector3.zero,
|
||||||
|
Quaternion.identity,
|
||||||
|
_originalScales[i]
|
||||||
|
);
|
||||||
|
|
||||||
|
Matrix4x4 newTransform = refTransform.localToWorldMatrix * Matrix4x4.TRS(
|
||||||
|
Vector3.zero,
|
||||||
|
Quaternion.identity,
|
||||||
|
gizmoScale
|
||||||
|
);
|
||||||
|
|
||||||
|
float scaleX = TransformVec(Vector3.right);
|
||||||
|
float scaleY = TransformVec(Vector3.up);
|
||||||
|
float scaleZ = TransformVec(Vector3.forward);
|
||||||
|
|
||||||
|
Undo.RecordObject(target, "Adjust scale");
|
||||||
|
var targetL2W = target.transform.localToWorldMatrix;
|
||||||
|
var baseToScaleCoord = (targetL2W * Matrix4x4.Scale(target.Scale)).inverse * targetL2W;
|
||||||
|
|
||||||
|
target.Scale = new Vector3(scaleX, scaleY, scaleZ);
|
||||||
|
|
||||||
|
var scaleToBaseCoord = Matrix4x4.Scale(target.Scale);
|
||||||
|
|
||||||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(target);
|
||||||
|
|
||||||
|
// Update child positions
|
||||||
|
if (_adjustChildPositions)
|
||||||
|
{
|
||||||
|
var updateTransform = scaleToBaseCoord * baseToScaleCoord;
|
||||||
|
foreach (Transform child in target.transform)
|
||||||
|
{
|
||||||
|
Undo.RecordObject(child, "Adjust scale");
|
||||||
|
child.localPosition = updateTransform.MultiplyPoint(child.localPosition);
|
||||||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float TransformVec(Vector3 vec)
|
||||||
|
{
|
||||||
|
// first, place our measurement vector into world spoce
|
||||||
|
vec = (initialTransform * initialScale).MultiplyVector(vec);
|
||||||
|
// now put it into reference space
|
||||||
|
vec = refTransform.worldToLocalMatrix.MultiplyVector(vec);
|
||||||
|
// and return using the adjusted scale
|
||||||
|
vec = newTransform.MultiplyVector(vec);
|
||||||
|
// now come back into local space
|
||||||
|
vec = (initialTransform.inverse).MultiplyVector(vec);
|
||||||
|
|
||||||
|
return vec.magnitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UnhideTools()
|
||||||
|
{
|
||||||
|
Tools.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInnerInspectorGUI()
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(_scale, G("scale_adjuster.scale"));
|
||||||
|
|
||||||
|
_adjustChildPositions = EditorGUILayout.Toggle(G("scale_adjuster.adjust_children"), _adjustChildPositions);
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
|
Localization.ShowLanguageUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
Editor/Inspector/ScaleAdjusterInspector.cs.meta
Normal file
3
Editor/Inspector/ScaleAdjusterInspector.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2632280a7a3445d68589d63458159b8c
|
||||||
|
timeCreated: 1703662098
|
@ -232,5 +232,7 @@
|
|||||||
"setup_outfit.err.no_animator": "Your avatar does not have an Animator component.",
|
"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_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:",
|
||||||
"move_independently.group-header": "Objects to move together"
|
"move_independently.group-header": "Objects to move together",
|
||||||
|
"scale_adjuster.scale": "Scale adjustment",
|
||||||
|
"scale_adjuster.adjust_children": "Adjust position of child objects"
|
||||||
}
|
}
|
@ -193,5 +193,7 @@
|
|||||||
"setup_outfit.err.no_animator": "アバターにAnimatorコンポーネントがありません。",
|
"setup_outfit.err.no_animator": "アバターにAnimatorコンポーネントがありません。",
|
||||||
"setup_outfit.err.no_hips": "アバターにHipsボーンがありません。なお、Setup Outfitはヒューマノイドアバター以外には対応していません。",
|
"setup_outfit.err.no_hips": "アバターにHipsボーンがありません。なお、Setup Outfitはヒューマノイドアバター以外には対応していません。",
|
||||||
"setup_outfit.err.no_outfit_hips": "衣装のHipsボーンを発見できませんでした。以下の名前を含むボーンを探しました:",
|
"setup_outfit.err.no_outfit_hips": "衣装のHipsボーンを発見できませんでした。以下の名前を含むボーンを探しました:",
|
||||||
"move_independently.group-header": "一緒に動かすオブジェクト"
|
"move_independently.group-header": "一緒に動かすオブジェクト",
|
||||||
|
"scale_adjuster.scale": "Scale調整値",
|
||||||
|
"scale_adjuster.adjust_children": "子オブジェクトの位置を調整"
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,11 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
TopoProcessMergeArmatures(mergeArmatures);
|
TopoProcessMergeArmatures(mergeArmatures);
|
||||||
|
|
||||||
#if MA_VRCSDK3_AVATARS
|
#if MA_VRCSDK3_AVATARS
|
||||||
|
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<ScaleProxy>(true))
|
||||||
|
{
|
||||||
|
BoneDatabase.AddMergedBone(c.transform);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBone>(true))
|
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBone>(true))
|
||||||
{
|
{
|
||||||
if (c.rootTransform == null) c.rootTransform = c.transform;
|
if (c.rootTransform == null) c.rootTransform = c.transform;
|
||||||
|
@ -56,7 +56,6 @@ namespace nadena.dev.modular_avatar.core.ArmatureAwase
|
|||||||
|
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
Debug.Log("=== OnValidate");
|
|
||||||
hideFlags = HideFlags.DontSave;
|
hideFlags = HideFlags.DontSave;
|
||||||
_excluded = new HashSet<Transform>();
|
_excluded = new HashSet<Transform>();
|
||||||
if (m_groupedBones == null)
|
if (m_groupedBones == null)
|
||||||
@ -110,6 +109,7 @@ namespace nadena.dev.modular_avatar.core.ArmatureAwase
|
|||||||
foreach (Transform child in parent)
|
foreach (Transform child in parent)
|
||||||
{
|
{
|
||||||
if (_excluded.Contains(child)) continue;
|
if (_excluded.Contains(child)) continue;
|
||||||
|
if (child.GetComponent<ScaleProxy>() != null) continue;
|
||||||
|
|
||||||
_observed.Add(child);
|
_observed.Add(child);
|
||||||
|
|
||||||
|
268
Runtime/ModularAvatarScaleAdjuster.cs
Normal file
268
Runtime/ModularAvatarScaleAdjuster.cs
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
#endif
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.core
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
internal struct ScalePatch
|
||||||
|
{
|
||||||
|
public SkinnedMeshRenderer smr;
|
||||||
|
public int boneIndex;
|
||||||
|
|
||||||
|
public ScalePatch(SkinnedMeshRenderer smr, int boneIndex)
|
||||||
|
{
|
||||||
|
this.smr = smr;
|
||||||
|
this.boneIndex = boneIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ScalePatch other)
|
||||||
|
{
|
||||||
|
return smr.Equals(other.smr) && boneIndex == other.boneIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is ScalePatch other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
|
{
|
||||||
|
return (smr.GetHashCode() * 397) ^ boneIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExecuteInEditMode]
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
[AddComponentMenu("Modular Avatar/MA Scale Adjuster")]
|
||||||
|
[HelpURL("https://modular-avatar.nadena.dev/docs/reference/scale-adjuster?lang=auto")]
|
||||||
|
public sealed class ModularAvatarScaleAdjuster : AvatarTagComponent
|
||||||
|
{
|
||||||
|
[SerializeField] private Vector3 m_Scale = Vector3.one;
|
||||||
|
|
||||||
|
public Vector3 Scale
|
||||||
|
{
|
||||||
|
get => m_Scale;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_Scale = value;
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] internal Transform scaleProxy;
|
||||||
|
|
||||||
|
[SerializeField] private List<ScalePatch> patches = new List<ScalePatch>();
|
||||||
|
|
||||||
|
private bool initialized = false;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (this == null) return;
|
||||||
|
|
||||||
|
PatchRenderers();
|
||||||
|
|
||||||
|
scaleProxy.localScale = m_Scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnValidate()
|
||||||
|
{
|
||||||
|
initialized = false;
|
||||||
|
EditorApplication.delayCall += Update;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PatchRenderers()
|
||||||
|
{
|
||||||
|
if (initialized || this == null) return;
|
||||||
|
|
||||||
|
if (PrefabUtility.IsPartOfPrefabInstance(this))
|
||||||
|
{
|
||||||
|
// Ensure we're using the same ScaleProxy as the corresponding prefab asset.
|
||||||
|
var prefab = PrefabUtility.GetCorrespondingObjectFromSource(this);
|
||||||
|
if (this.scaleProxy == null || prefab.scaleProxy == null || prefab.scaleProxy !=
|
||||||
|
PrefabUtility.GetCorrespondingObjectFromSource(this.scaleProxy))
|
||||||
|
{
|
||||||
|
if (prefab.scaleProxy == null && scaleProxy != null)
|
||||||
|
{
|
||||||
|
// Push our ScaleProxy down into the prefab (this happens after applying the ScaleAdjuster
|
||||||
|
// component to a prefab)
|
||||||
|
var assetPath = AssetDatabase.GetAssetPath(prefab);
|
||||||
|
PrefabUtility.ApplyAddedGameObject(scaleProxy.gameObject, assetPath,
|
||||||
|
InteractionMode.AutomatedAction);
|
||||||
|
prefab.scaleProxy = PrefabUtility.GetCorrespondingObjectFromSource(this.scaleProxy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Clear any duplicate scaleProxy we have
|
||||||
|
|
||||||
|
if (scaleProxy != null) DestroyImmediate(scaleProxy.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
var so = new SerializedObject(this);
|
||||||
|
var sp = so.FindProperty(nameof(scaleProxy));
|
||||||
|
PrefabUtility.RevertPropertyOverride(sp, InteractionMode.AutomatedAction);
|
||||||
|
so.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
// Find the corresponding child
|
||||||
|
foreach (Transform t in transform)
|
||||||
|
{
|
||||||
|
if (PrefabUtility.GetCorrespondingObjectFromSource(t) == prefab.scaleProxy)
|
||||||
|
{
|
||||||
|
scaleProxy = t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scaleProxy == null && !PrefabUtility.IsPartOfPrefabAsset(this))
|
||||||
|
{
|
||||||
|
scaleProxy = new GameObject(gameObject.name + " (Scale Proxy)").transform;
|
||||||
|
scaleProxy.SetParent(transform, false);
|
||||||
|
scaleProxy.localPosition = Vector3.zero;
|
||||||
|
scaleProxy.localRotation = Quaternion.identity;
|
||||||
|
scaleProxy.localScale = m_Scale;
|
||||||
|
scaleProxy.gameObject.AddComponent<ScaleProxy>();
|
||||||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scaleProxy != null)
|
||||||
|
{
|
||||||
|
scaleProxy.hideFlags = HideFlags.HideInHierarchy;
|
||||||
|
|
||||||
|
RewriteBoneReferences(transform, scaleProxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RewriteBoneReferences(Transform oldBone, Transform newBone, Transform selfTransform = null)
|
||||||
|
{
|
||||||
|
if (selfTransform == null) selfTransform = transform;
|
||||||
|
|
||||||
|
var prefabNewBone = PrefabUtility.GetCorrespondingObjectFromSource(newBone);
|
||||||
|
|
||||||
|
var oldPatches = new HashSet<ScalePatch>(this.patches);
|
||||||
|
var newPatches = new HashSet<ScalePatch>();
|
||||||
|
var avatarRoot = RuntimeUtil.FindAvatarInParents(selfTransform);
|
||||||
|
|
||||||
|
if (avatarRoot != null)
|
||||||
|
{
|
||||||
|
foreach (var smr in avatarRoot.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||||
|
{
|
||||||
|
var serializedObject = new SerializedObject(smr);
|
||||||
|
var bonesArray = serializedObject.FindProperty("m_Bones");
|
||||||
|
int boneCount = bonesArray.arraySize;
|
||||||
|
|
||||||
|
var parentSmr = PrefabUtility.GetCorrespondingObjectFromSource(smr);
|
||||||
|
var parentBones = parentSmr != null ? parentSmr.bones : null;
|
||||||
|
var propMods = PrefabUtility.GetPropertyModifications(smr);
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < boneCount; i++)
|
||||||
|
{
|
||||||
|
var boneProp = bonesArray.GetArrayElementAtIndex(i);
|
||||||
|
var bone = boneProp.objectReferenceValue as Transform;
|
||||||
|
if (bone == oldBone || bone == newBone ||
|
||||||
|
(bone == null && oldPatches.Contains(new ScalePatch(smr, i))))
|
||||||
|
{
|
||||||
|
if (parentBones != null && parentBones[i] == prefabNewBone)
|
||||||
|
{
|
||||||
|
// Remove any prefab overrides for this bone entry
|
||||||
|
changed = boneProp.objectReferenceValue != newBone;
|
||||||
|
boneProp.objectReferenceValue = newBone;
|
||||||
|
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
PrefabUtility.RevertPropertyOverride(boneProp, InteractionMode.AutomatedAction);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boneProp.objectReferenceValue = newBone;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPatches.Add(new ScalePatch(smr, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
ConfigurePrefab();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this != null && newPatches != oldPatches)
|
||||||
|
{
|
||||||
|
this.patches = newPatches.ToList();
|
||||||
|
PrefabUtility.RecordPrefabInstancePropertyModifications(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigurePrefab()
|
||||||
|
{
|
||||||
|
if (this == null || !PrefabUtility.IsPartOfPrefabInstance(this)) return;
|
||||||
|
var source = PrefabUtility.GetCorrespondingObjectFromSource(this);
|
||||||
|
var path = AssetDatabase.GetAssetPath(source);
|
||||||
|
var root = PrefabUtility.LoadPrefabContents(path);
|
||||||
|
|
||||||
|
foreach (var obj in root.GetComponentsInChildren<ModularAvatarScaleAdjuster>())
|
||||||
|
{
|
||||||
|
obj.PatchRenderers();
|
||||||
|
}
|
||||||
|
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(root, path);
|
||||||
|
PrefabUtility.UnloadPrefabContents(root);
|
||||||
|
|
||||||
|
initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
base.OnDestroy();
|
||||||
|
|
||||||
|
UnpatchRenderers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnpatchRenderers()
|
||||||
|
{
|
||||||
|
var scaleProxy2 = this.scaleProxy;
|
||||||
|
var transform2 = this.transform;
|
||||||
|
|
||||||
|
EditorApplication.delayCall += () =>
|
||||||
|
{
|
||||||
|
if (scaleProxy2 == null) return;
|
||||||
|
|
||||||
|
if (transform2 != null)
|
||||||
|
{
|
||||||
|
RewriteBoneReferences(scaleProxy2, transform2, transform2);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DestroyImmediate(scaleProxy2.gameObject);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException e)
|
||||||
|
{
|
||||||
|
// not supported in Unity 2019...
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
// placeholder to make builds work
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/ModularAvatarScaleAdjuster.cs.meta
Normal file
11
Runtime/ModularAvatarScaleAdjuster.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 09a660aa9d4e47d992adcac5a05dd808
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
82
Runtime/ScaleProxy.cs
Normal file
82
Runtime/ScaleProxy.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
#endif
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace nadena.dev.modular_avatar.core
|
||||||
|
{
|
||||||
|
[AddComponentMenu("")]
|
||||||
|
internal sealed class ScaleProxy : AvatarTagComponent
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
void OnValidate()
|
||||||
|
{
|
||||||
|
EditorApplication.delayCall += DeferredValidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeferredValidate()
|
||||||
|
{
|
||||||
|
if (this == null) return;
|
||||||
|
|
||||||
|
gameObject.hideFlags = HideFlags.HideInHierarchy;
|
||||||
|
|
||||||
|
var parentObject = transform.parent;
|
||||||
|
var parentScaleAdjuster =
|
||||||
|
parentObject != null ? parentObject.GetComponent<ModularAvatarScaleAdjuster>() : null;
|
||||||
|
|
||||||
|
if (parentScaleAdjuster == null || parentScaleAdjuster.scaleProxy != transform)
|
||||||
|
{
|
||||||
|
if (PrefabUtility.IsPartOfPrefabAsset(this))
|
||||||
|
{
|
||||||
|
var path = AssetDatabase.GetAssetPath(this);
|
||||||
|
var root = PrefabUtility.LoadPrefabContents(path);
|
||||||
|
|
||||||
|
foreach (var obj in root.GetComponentsInChildren<ScaleProxy>())
|
||||||
|
{
|
||||||
|
obj.DeferredValidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(root, path);
|
||||||
|
PrefabUtility.UnloadPrefabContents(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelfDestruct();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelfDestruct()
|
||||||
|
{
|
||||||
|
var root = ndmf.runtime.RuntimeUtil.FindAvatarInParents(transform);
|
||||||
|
if (root == null)
|
||||||
|
{
|
||||||
|
root = transform;
|
||||||
|
while (root.parent != null) root = root.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var smr in root.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
||||||
|
{
|
||||||
|
var bones = smr.bones;
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < bones.Length; i++)
|
||||||
|
{
|
||||||
|
if (bones[i] == transform)
|
||||||
|
{
|
||||||
|
bones[i] = transform.parent;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
smr.bones = bones;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DestroyImmediate(gameObject);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
3
Runtime/ScaleProxy.cs.meta
Normal file
3
Runtime/ScaleProxy.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3f0c19b32ba845a2a84f37f48e4ec4d5
|
||||||
|
timeCreated: 1703659053
|
@ -39,6 +39,7 @@ namespace modular_avatar_tests
|
|||||||
if (type == typeof(Activator)) return;
|
if (type == typeof(Activator)) return;
|
||||||
if (type == typeof(AvatarActivator)) return;
|
if (type == typeof(AvatarActivator)) return;
|
||||||
if (type == typeof(TestComponent)) return;
|
if (type == typeof(TestComponent)) return;
|
||||||
|
if (type == typeof(ScaleProxy)) return;
|
||||||
|
|
||||||
// get icon
|
// get icon
|
||||||
var component = (MonoBehaviour) _gameObject.AddComponent(type);
|
var component = (MonoBehaviour) _gameObject.AddComponent(type);
|
||||||
@ -63,6 +64,7 @@ namespace modular_avatar_tests
|
|||||||
if (type == typeof(Activator)) return;
|
if (type == typeof(Activator)) return;
|
||||||
if (type == typeof(AvatarActivator)) return;
|
if (type == typeof(AvatarActivator)) return;
|
||||||
if (type == typeof(TestComponent)) return;
|
if (type == typeof(TestComponent)) return;
|
||||||
|
if (type == typeof(ScaleProxy)) return;
|
||||||
|
|
||||||
// get icon
|
// get icon
|
||||||
var helpUrl = type.GetCustomAttribute<HelpURLAttribute>();
|
var helpUrl = type.GetCustomAttribute<HelpURLAttribute>();
|
||||||
|
@ -20,4 +20,6 @@ For example, you might move the hips and upper leg objects together, but leave t
|
|||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
While this component supports scaling an object independently of its children, non-uniform scales (where the X, Y, and Z
|
While this component supports scaling an object independently of its children, non-uniform scales (where the X, Y, and Z
|
||||||
scales are not all the same) are not fully supported, and may result in unexpected behavior.
|
scales are not all the same) are not fully supported, and may result in unexpected behavior. If you need to adjust the
|
||||||
|
scale of each axis independently, you should use the [Scale Adjuster](scale-adjuster.md) component in addition to Move
|
||||||
|
Independently.
|
||||||
|
BIN
docs~/docs/reference/scale-adjuster-warning-trs-tool.png
Normal file
BIN
docs~/docs/reference/scale-adjuster-warning-trs-tool.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
37
docs~/docs/reference/scale-adjuster.md
Normal file
37
docs~/docs/reference/scale-adjuster.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Scale Adjuster
|
||||||
|
|
||||||
|
![Scale Adjuster](scale-adjuster.png)
|
||||||
|
|
||||||
|
The Scale Adjuster component allows you to adjust the X/Y/Z scales of a specific bone without causing issues with
|
||||||
|
rotated child bones.
|
||||||
|
|
||||||
|
## When should I use it?
|
||||||
|
|
||||||
|
This component is primarily intended for use when fitting clothing not originally designed for your avatar. You can use
|
||||||
|
this to adjust the dimensions of specific bones, without breaking child bones.
|
||||||
|
|
||||||
|
## When shouldn't I use it?
|
||||||
|
|
||||||
|
When adjusting the overall scale of a bone (where X/Y/Z are being adjusted equally), it's usually better to use the
|
||||||
|
normal unity scale tools.
|
||||||
|
|
||||||
|
## Setting up Scale Adjuster
|
||||||
|
|
||||||
|
Simply add the Scale Adjuster component to the bone in question. Now, when you have the scaling tool selected, changes
|
||||||
|
will affect only this one bone.
|
||||||
|
|
||||||
|
You can check or uncheck the "Adjust child positions" checkbox to adjust the relative position of child bones when the
|
||||||
|
scale of their parent changes. This is useful when you want to adjust the scale of a bone, but don't want to move the
|
||||||
|
child bones. Note that this adjusts only the _position_ of child bones, and not their scale.
|
||||||
|
|
||||||
|
Scale Adjuster supports adjusting the scale of multiple bones by adding the Scale Component to all of the bones in
|
||||||
|
question, then selecting multiple bones before adjusting their scale. However, if these bones are rotated, the scale
|
||||||
|
adjustment won't be perfect, and may not give quite the results you expect.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
![Use this, not that](scale-adjuster-warning-trs-tool.png)
|
||||||
|
|
||||||
|
Scale Adjuster only controls the unity scale tool. The combined Move/Rotate/Scale tool will still affect all children.
|
||||||
|
|
||||||
|
:::
|
BIN
docs~/docs/reference/scale-adjuster.png
Normal file
BIN
docs~/docs/reference/scale-adjuster.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
@ -20,4 +20,4 @@ MA Move Independentlyというコンポーネントを使うと、子オブジ
|
|||||||
## 制限事項
|
## 制限事項
|
||||||
|
|
||||||
子に影響を与えずにオブジェクトの大きさを調整することは対応していますが、XYZそれぞれのスケールが同じ出ない場合は
|
子に影響を与えずにオブジェクトの大きさを調整することは対応していますが、XYZそれぞれのスケールが同じ出ない場合は
|
||||||
対応されず、変な挙動になることがあります。ご注意ください。
|
対応されず、変な挙動になることがあります。個別に調整する必要がある場合は、「[Scale Adjuster](scale-adjuster.md)」を併用しましょう。
|
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
@ -0,0 +1,32 @@
|
|||||||
|
# Scale Adjuster
|
||||||
|
|
||||||
|
![Scale Adjuster](scale-adjuster.png)
|
||||||
|
|
||||||
|
Scale Adjusterを付けることで、回転した子ボーンに影響を与えることなく、ボーンのX/Y/Zスケールを個別に調整することができます。
|
||||||
|
|
||||||
|
## いつ使うべきか?
|
||||||
|
|
||||||
|
このコンポーネントは、非対応衣装を導入するときに利用することを想定しています。これを利用すると、子ボーンを壊すことなく、
|
||||||
|
特定のボーンの寸法を調整できます。
|
||||||
|
|
||||||
|
## 非推奨の場合
|
||||||
|
|
||||||
|
ボーンの全体のスケールを変更する場合(X/Y/Zが連動して変わる場合)は、Unity標準のスケールツールを使いましょう。
|
||||||
|
|
||||||
|
## 設定方法
|
||||||
|
|
||||||
|
該当のボーンにScale Adjusterコンポーネントを追加します。これで、スケールツールを選択したときに、このボーンのみに影響が出ます。
|
||||||
|
|
||||||
|
「子オブジェクトの位置を調整」という設定を入れると、親のスケールが変わる時は子ボーンの相対位置も調整されます。逆にオフにすることで、
|
||||||
|
子ボーンを移動させずに親のスケールを調整できます。なお、子ボーンの位置のみが調整されます。子ボーンのスケールが変更されません。
|
||||||
|
|
||||||
|
複数のボーンのスケールを同時に調整することも対応しています。該当するボーンすべてにScale Adjusterを追加して、複数選択でスケール調整
|
||||||
|
するとすべて同時に編集できます。ただし、ボーンが回転された場合は完璧に連動が取れない場合があります。ご注意ください。
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
![こっちのほうを使いましょう](scale-adjuster-warning-trs-tool.png)
|
||||||
|
|
||||||
|
Scale Adjusterはスケール(拡大縮小)ツールのみに対応しています。移動・回転・スケールをまとめたツールでは、将来通り子ボーンにも影響が出ます。
|
||||||
|
|
||||||
|
:::
|
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
Loading…
Reference in New Issue
Block a user