From e04d0248af457c86770efb3cb132121def94a96f Mon Sep 17 00:00:00 2001 From: ousttrue Date: Fri, 6 Feb 2026 15:58:00 +0900 Subject: [PATCH] impl importer, exporter and editor --- .../Expression/ExpressionEditorHelper.cs | 15 ++ .../Expression/PreviewFaceRenderer.cs | 4 + .../ReorderableNodeTransformBindingList.cs | 131 ++++++++++++++++++ ...orderableNodeTransformBindingList.cs.meta} | 2 +- .../Expression/SerializedExpressionEditor.cs | 45 +++--- .../Expression/BoneTransformMerger.cs | 41 ------ .../Components/Expression/ExpressionMerger.cs | 13 +- .../Expression/NodeTransformBindingMerger.cs | 51 +++++++ ...eta => NodeTransformBindingMerger.cs.meta} | 0 .../Expression/NodeTransformtBinding.cs | 53 +++++++ .../Expression/NodeTransformtBinding.cs.meta | 11 ++ .../Expression/Preview/PreviewSceneManager.cs | 47 +++++-- .../Components/Expression/VRM10Expression.cs | 6 + .../Vrm10BoneTransformExpression.cs | 22 --- .../Components/Vrm10Runtime/Vrm10Runtime.cs | 4 +- .../Vrm10Runtime/Vrm10RuntimeExpression.cs | 16 ++- .../VRM10/Runtime/IO/ExpressionExtensions.cs | 25 ++++ Packages/VRM10/Runtime/IO/Vrm10Exporter.cs | 52 ++++++- Packages/VRM10/Runtime/IO/Vrm10Importer.cs | 20 +++ 19 files changed, 452 insertions(+), 106 deletions(-) create mode 100644 Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs rename Packages/VRM10/{Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta => Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta} (83%) delete mode 100644 Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs create mode 100644 Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs rename Packages/VRM10/Runtime/Components/Expression/{BoneTransformMerger.cs.meta => NodeTransformBindingMerger.cs.meta} (100%) create mode 100644 Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs create mode 100644 Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta delete mode 100644 Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs diff --git a/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs b/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs index 4791a460a..bc23218ea 100644 --- a/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs +++ b/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs @@ -99,6 +99,21 @@ namespace UniVRM10 } } + public static bool Vec3Prop(Rect rect, SerializedProperty prop) + { + var oldValue = prop.vector3Value; + var newValue = EditorGUI.Vector3Field(rect, prop.displayName, oldValue); + if (newValue != oldValue) + { + prop.vector3Value = newValue; + return true; + } + else + { + return false; + } + } + static Rect AdvanceRect(ref float x, float y, float w, float h) { var rect = new Rect(x, y, w, h); diff --git a/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs b/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs index 4020fcf06..db49a99c0 100644 --- a/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs +++ b/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs @@ -110,6 +110,10 @@ namespace UniVRM10 int subMeshCount = item.Mesh.subMeshCount; for (int i = 0; i < subMeshCount; i++) { + if (item.SkinnedMeshRenderer != null) + { + item.SkinnedMeshRenderer.BakeMesh(item.Mesh); + } m_previewUtility.DrawMesh(item.Mesh, item.Position, item.Rotation, item.Materials[i], i); diff --git a/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs new file mode 100644 index 000000000..1903f7014 --- /dev/null +++ b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs @@ -0,0 +1,131 @@ +using System; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + + +namespace UniVRM10 +{ + public class ReorderableNodeTransformBindingList + { + ReorderableList m_ValuesList; + SerializedProperty m_valuesProp; + bool m_changed; + + public ReorderableNodeTransformBindingList(SerializedObject serializedObject, PreviewSceneManager previewSceneManager, int height) + { + m_valuesProp = serializedObject.FindProperty(nameof(VRM10Expression.NodeTransformBindings)); + m_ValuesList = new ReorderableList(serializedObject, m_valuesProp); + m_ValuesList.elementHeight = height * 4; + m_ValuesList.drawElementCallback = + (rect, index, isActive, isFocused) => + { + var element = m_valuesProp.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + if (DrawNodeTransformBinding(rect, element, previewSceneManager, height)) + { + m_changed = true; + } + }; + } + + /// + /// NodeTransform List のElement描画 + /// + static bool DrawNodeTransformBinding(Rect position, SerializedProperty property, + PreviewSceneManager scene, int height) + { + bool changed = false; + if (scene != null) + { + var y = position.y; + var rect = new Rect(position.x, y, position.width, height); + int pathIndex; + if (ExpressionEditorHelper.StringPopup(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.RelativePath)), scene.NodeTransformPathList, out pathIndex)) + { + changed = true; + } + + // T` + y += height; + rect = new Rect(position.x, y, position.width, height); + if (ExpressionEditorHelper.Vec3Prop(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.OffsetTranslation)))) + { + changed = true; + } + + // R + y += height; + rect = new Rect(position.x, y, position.width, height); + if (EditorGUI.PropertyField(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.OffsetRotation)))) + { + changed = true; + } + + // S + y += height; + rect = new Rect(position.x, y, position.width, height); + if (ExpressionEditorHelper.Vec3Prop(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.TargetScale)))) + { + changed = true; + } + } + return changed; + } + + public void SetValues(NodeTransformBinding[] bindings) + { + m_valuesProp.ClearArray(); + m_valuesProp.arraySize = bindings.Length; + for (int i = 0; i < bindings.Length; ++i) + { + var item = m_valuesProp.GetArrayElementAtIndex(i); + + var endProperty = item.GetEndProperty(); + while (item.NextVisible(true)) + { + if (SerializedProperty.EqualContents(item, endProperty)) + { + break; + } + + switch (item.name) + { + case nameof(NodeTransformBinding.RelativePath): + item.stringValue = bindings[i].RelativePath; + break; + + case nameof(NodeTransformBinding.OffsetTranslation): + item.vector3Value = bindings[i].OffsetTranslation; + break; + + case nameof(NodeTransformBinding.OffsetRotation): + item.quaternionValue = bindings[i].OffsetRotation; + break; + + case nameof(NodeTransformBinding.TargetScale): + item.vector3Value = bindings[i].TargetScale; + break; + + default: + throw new Exception(); + } + } + } + + } + + public bool Draw(string label) + { + m_changed = false; + m_ValuesList.DoLayoutList(); + if (GUILayout.Button($"Clear {label}")) + { + m_changed = true; + m_valuesProp.arraySize = 0; + } + return m_changed; + } + } +} diff --git a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta similarity index 83% rename from Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta rename to Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta index 1622ca50d..3c72f298b 100644 --- a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta +++ b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1520687d88c34af4eb8c16bb37ee0391 +guid: 186e6dd105c86904497f3ac0dfd61212 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs b/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs index 782d355a3..768cc15f0 100644 --- a/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs +++ b/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using UnityEditor; @@ -28,21 +29,24 @@ namespace UniVRM10 ReorderableMorphTargetBindingList m_morphTargetBindings; ReorderableMaterialColorBindingList m_materialColorBindings; ReorderableMaterialUVBindingList m_materialUVBindings; - + ReorderableNodeTransformBindingList m_nodeTransformBindings; #region Editor values bool m_changed; - static int s_Mode; static bool s_MorphTargetFoldout = true; static bool s_OptionFoldout; static bool s_ListFoldout; - static string[] MODES = new[]{ - "MorphTarget", - "Material Color", - "Texture Transform" - }; + enum ListMode + { + MorphTarget, + MaterialColor, + TextureTransform, + NodeTransform, + } + static ListMode s_Mode; + static string[] MODES = ((ListMode[])Enum.GetValues(typeof(ListMode))).Select(x => x.ToString()).ToArray(); PreviewMeshItem[] m_items; #endregion @@ -71,6 +75,7 @@ namespace UniVRM10 m_morphTargetBindings = new ReorderableMorphTargetBindingList(serializedObject, previewSceneManager, 20); m_materialColorBindings = new ReorderableMaterialColorBindingList(serializedObject, previewSceneManager?.MaterialNames, 20); m_materialUVBindings = new ReorderableMaterialUVBindingList(serializedObject, previewSceneManager?.MaterialNames, 20); + m_nodeTransformBindings = new ReorderableNodeTransformBindingList(serializedObject, previewSceneManager, 20); m_items = previewSceneManager.EnumRenderItems .Where(x => x.SkinnedMeshRenderer != null) @@ -109,33 +114,39 @@ namespace UniVRM10 if (s_ListFoldout) { EditorGUI.indentLevel++; - s_Mode = GUILayout.Toolbar(s_Mode, MODES); + s_Mode = (ListMode)GUILayout.Toolbar((int)s_Mode, MODES); switch (s_Mode) { - case 0: - // MorphTarget + case ListMode.MorphTarget: { - if (m_morphTargetBindings.Draw("MorphTarget")) + if (m_morphTargetBindings.Draw(s_Mode.ToString())) { m_changed = true; } } break; - case 1: - // Material + case ListMode.MaterialColor: { - if (m_materialColorBindings.Draw("MaterialColor")) + if (m_materialColorBindings.Draw(s_Mode.ToString())) { m_changed = true; } } break; - case 2: - // TextureTransform + case ListMode.TextureTransform: { - if (m_materialUVBindings.Draw("TextureTransform")) + if (m_materialUVBindings.Draw(s_Mode.ToString())) + { + m_changed = true; + } + } + break; + + case ListMode.NodeTransform: + { + if (m_nodeTransformBindings.Draw(s_Mode.ToString())) { m_changed = true; } diff --git a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs deleted file mode 100644 index 114edf0c5..000000000 --- a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using UniGLTF; -using UniGLTF.Utils; -using UnityEngine; - - -namespace UniVRM10 -{ - internal sealed class BoneTransformBindingMerger - { - Dictionary _acum = new(); - // Vrm10BoneTransformExpression[] _expressions; - Transform _root; - - public BoneTransformBindingMerger(Transform root) - { - // _expressions = root.GetComponentsInChildren(); - _root = root; - } - - public void AccumulateValue(ExpressionKey key, float value) - { - _acum[key] = value; - } - - public void Apply(IReadOnlyDictionary initPose) - { - foreach (var expression in _root.GetComponentsInChildren()) - { - if (initPose.TryGetValue(expression.transform, out var init)) - { - var weight = _acum.GetValueOrDefault(expression.Expression.ExpressionKey, 0); - expression.transform.SetLocalPositionAndRotation( - init.LocalPosition + expression.Expression.Translation * weight, - Quaternion.Slerp(init.LocalRotation, init.LocalRotation * expression.Expression.Rotation, weight) - ); - } - } - } - } -} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs index 4d85995f5..de9a6a55f 100644 --- a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs +++ b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs @@ -24,10 +24,13 @@ namespace UniVRM10 MorphTargetBindingMerger m_morphTargetBindingMerger; MaterialValueBindingMerger m_materialValueBindingMerger; - BoneTransformBindingMerger m_boneTransformBindingMerger; + NodeTransformBindingMerger m_boneTransformBindingMerger; - public ExpressionMerger(VRM10ObjectExpression expressions, Transform root, bool isPrefabInstance) + public ExpressionMerger(VRM10ObjectExpression expressions, + Transform root, + bool isPrefabInstance, + IReadOnlyDictionary initPose) { m_clipMap = expressions.Clips.ToDictionary( x => expressions.CreateKey(x.Clip), @@ -37,14 +40,14 @@ namespace UniVRM10 m_valueMap = new Dictionary(ExpressionKey.Comparer); m_morphTargetBindingMerger = new MorphTargetBindingMerger(m_clipMap, root); m_materialValueBindingMerger = new MaterialValueBindingMerger(m_clipMap, root, isPrefabInstance); - m_boneTransformBindingMerger = new BoneTransformBindingMerger(root); + m_boneTransformBindingMerger = new NodeTransformBindingMerger(m_clipMap, root, initPose); } /// /// まとめて反映する。1フレームに1回呼び出されることを想定 /// /// - public void SetValues(Dictionary expressionWeights, IReadOnlyDictionary initPose) + public void SetValues(Dictionary expressionWeights) { foreach (var (key, weight) in expressionWeights) { @@ -53,7 +56,7 @@ namespace UniVRM10 m_morphTargetBindingMerger.Apply(); m_materialValueBindingMerger.Apply(); - m_boneTransformBindingMerger.Apply(initPose); + m_boneTransformBindingMerger.Apply(); } private void AccumulateValue(ExpressionKey key, float value) diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs new file mode 100644 index 000000000..c972a8437 --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using UniGLTF.Utils; +using UnityEngine; + + +namespace UniVRM10 +{ + internal sealed class NodeTransformBindingMerger + { + IReadOnlyDictionary _clipMap; + Transform _root; + IReadOnlyDictionary _initPose; + Dictionary _weightMap = new(); + + public NodeTransformBindingMerger( + IReadOnlyDictionary clipMap, + Transform root, + IReadOnlyDictionary initPose) + { + _clipMap = clipMap; + _root = root; + _initPose = initPose; + } + + public void AccumulateValue(ExpressionKey key, float value) + { + _weightMap[key] = value; + } + + public void Apply() + { + foreach (var (k, weight) in _weightMap) + { + if (_clipMap.TryGetValue(k, out var clip)) + { + foreach (var b in clip.NodeTransformBindings) + { + var node = _root.GetFromPath(b.RelativePath); + if (node != null) + { + if (_initPose.TryGetValue(node, out var init)) + { + b.Apply(node, init, weight); + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs.meta b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs.meta similarity index 100% rename from Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs.meta rename to Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs.meta diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs new file mode 100644 index 000000000..d049fb858 --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs @@ -0,0 +1,53 @@ +using System; +using UniGLTF.Utils; +using UnityEngine; + +namespace UniVRM10 +{ + [Serializable] + public struct NodeTransformBinding + { + public string RelativePath; + + /// + /// t = init_t + offset_t * weight + /// disable if offset_t = (0, 0, 0) + /// + public Vector3 OffsetTranslation; + + /// + /// r = slerp(init_t, init_t * offset r, weight) + /// disable if rotation_t = (0, 0, 0, 1) + /// + public Quaternion OffsetRotation; + + /// + /// s = lerp(init_s, blend_s, weight) + /// disalbe if blend_s = init_s. maybe(1, 1, 1) + /// + public Vector3 TargetScale; + + /// + /// + /// + /// + /// + /// 0 to 1.0 + public NodeTransformBinding(string path, in Vector3 t, in Quaternion r, in Vector3 s) + { + RelativePath = path; + OffsetTranslation = t; + OffsetRotation = r; + TargetScale = s; + } + + public void Apply(Transform node, in TransformState init, float weight) + { + node.SetLocalPositionAndRotation( + init.LocalPosition + this.OffsetTranslation * weight, + Quaternion.Slerp(init.LocalRotation, init.LocalRotation * this.OffsetRotation, weight) + ); + node.localScale = Vector3.Lerp(init.LocalScale, this.TargetScale, weight); + } + } +} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta new file mode 100644 index 000000000..bb06db17f --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd660f7a77373234c85064f97150eef6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs index c93504344..30bdbe5a2 100644 --- a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs +++ b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs @@ -3,6 +3,7 @@ using System.Linq; using UnityEngine; using System; using UniGLTF; +using UniGLTF.Utils; namespace UniVRM10 @@ -16,6 +17,8 @@ namespace UniVRM10 public bool hasError; + private IReadOnlyDictionary m_defaultTransformStates; + #if UNITY_EDITOR public static PreviewSceneManager GetOrCreate(GameObject prefab) { @@ -85,7 +88,7 @@ namespace UniVRM10 private void Initialize(GameObject prefab) { hasError = false; - + Prefab = prefab; var materialNames = new List(); @@ -104,14 +107,14 @@ namespace UniVRM10 { dst = new Material(src); map.Add(src, dst); - + if (!PreviewMaterialUtil.TryCreateForPreview(dst, out var previewMaterialItem)) { hasError = true; // Return cloned material for preview return dst; } - + m_materialMap.Add(src.name, previewMaterialItem); materialNames.Add(src.name); @@ -119,6 +122,10 @@ namespace UniVRM10 return dst; }; + // initPose for nodeTransform + m_defaultTransformStates = transform.GetComponentsInChildren() + .ToDictionary(tf => tf, tf => new TransformState(tf)); + m_meshes = transform.Traverse() .Select(x => PreviewMeshItem.Create(x, transform, getOrCreateMaterial)) .Where(x => x != null) @@ -136,8 +143,13 @@ namespace UniVRM10 .Where(x => x.SkinnedMeshRenderer != null) .Select(x => x.Path) .ToArray(); + m_nodeTransformPathList = transform.GetComponentsInChildren() + .Select(x => x.RelativePathFrom(transform)) + .Where(x => x != null) + .ToArray() + ; - if(TryGetComponent(out var animator)) + if (TryGetComponent(out var animator)) { var head = animator.GetBoneTransform(HumanBodyBones.Head); if (head != null) @@ -184,6 +196,12 @@ namespace UniVRM10 get { return m_skinnedMeshRendererPathList; } } + string[] m_nodeTransformPathList; + public string[] NodeTransformPathList + { + get { return m_nodeTransformPathList; } + } + public string[] GetBlendShapeNames(int blendShapeMeshIndex) { if (blendShapeMeshIndex >= 0 && blendShapeMeshIndex < m_blendShapeMeshes.Length) @@ -228,19 +246,28 @@ namespace UniVRM10 return; } + // + // bake NodeTransform + // + foreach (var nodeTransform in bake.NodeTransformBindings) + { + var node = transform.GetFromPath(nodeTransform.RelativePath); + if (m_defaultTransformStates.TryGetValue(node, out var init)) + { + nodeTransform.Apply(node, init, weight); + } + } + // // Bake Expression // m_bounds = default(Bounds); if (m_meshes != null) { - if (bake != null) + foreach (var x in m_meshes) { - foreach (var x in m_meshes) - { - x.Bake(bake.MorphTargetBindings, weight); - m_bounds.Expand(x.Mesh.bounds.size); - } + x.Bake(bake.MorphTargetBindings, weight); + m_bounds.Expand(x.Mesh.bounds.size); } } diff --git a/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs b/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs index b75473e24..d0fd471d3 100644 --- a/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs +++ b/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs @@ -46,5 +46,11 @@ namespace UniVRM10 /// [SerializeField] public UniGLTF.Extensions.VRMC_vrm.ExpressionOverrideType OverrideMouth; + + /// + /// from UniVRM-132.0. experimental + /// + [SerializeField] + public NodeTransformBinding[] NodeTransformBindings = new NodeTransformBinding[] { }; } } diff --git a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs b/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs deleted file mode 100644 index 1f33be137..000000000 --- a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using UniGLTF.Utils; -using UnityEngine; - -namespace UniVRM10 -{ - [DisallowMultipleComponent] - public class Vrm10BoneTransformExpression : MonoBehaviour - { - [Serializable] - public class BoneTransformExpression - { - public ExpressionPreset Preset = ExpressionPreset.custom; - public string Name = "custom"; - public ExpressionKey ExpressionKey => new(Preset, Name); - public Quaternion Rotation = Quaternion.identity; - public Vector3 Translation = Vector3.zero; - } - - public BoneTransformExpression Expression; - } -} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs index 46429762e..e22956304 100644 --- a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs +++ b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs @@ -77,7 +77,7 @@ namespace UniVRM10 } Constraints = instance.GetComponentsInChildren(); LookAt = new Vrm10RuntimeLookAt(instance, instance.Humanoid, ControlRig); - Expression = new Vrm10RuntimeExpression(instance, LookAt.EyeDirectionApplicable, isPrefabInstance); + Expression = new Vrm10RuntimeExpression(instance, LookAt.EyeDirectionApplicable, isPrefabInstance, initPose); SpringBone = springBoneRuntime; } @@ -153,7 +153,7 @@ namespace UniVRM10 // 5. Apply Expression // LookAt の角度制限などはこちらで処理されます。 - Expression.Process(eyeDirection, _initPose); + Expression.Process(eyeDirection); // 6. SpringBone SpringBone.Process(Time.deltaTime); diff --git a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs index 276735700..3c13e449f 100644 --- a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs +++ b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs @@ -25,9 +25,13 @@ namespace UniVRM10 public float LookAtOverrideRate { get; private set; } public float MouthOverrideRate { get; private set; } - internal Vrm10RuntimeExpression(Vrm10Instance target, ILookAtEyeDirectionApplicable eyeDirectionApplicable, bool isPrefabInstance) + internal Vrm10RuntimeExpression(Vrm10Instance target, + ILookAtEyeDirectionApplicable eyeDirectionApplicable, + bool isPrefabInstance, + IReadOnlyDictionary initPose + ) { - _merger = new ExpressionMerger(target.Vrm.Expression, target.transform, isPrefabInstance); + _merger = new ExpressionMerger(target.Vrm.Expression, target.transform, isPrefabInstance, initPose); _keys = target.Vrm.Expression.Clips .Select(x => target.Vrm.Expression.CreateKey(x.Clip)) .ToList(); @@ -60,9 +64,9 @@ namespace UniVRM10 _eyeDirectionApplicable = null; } - internal void Process(LookAtEyeDirection inputEyeDirection, IReadOnlyDictionary initPose = null) + internal void Process(LookAtEyeDirection inputEyeDirection) { - Apply(inputEyeDirection, initPose); + Apply(inputEyeDirection); } public IDictionary GetWeights() @@ -114,7 +118,7 @@ namespace UniVRM10 /// 入力 Weight を基に、Validation を行い実際にモデルに適用される Weights を計算し、Merger を介して適用する。 /// この際、LookAt の情報を pull してそれも適用する。 /// - private void Apply(LookAtEyeDirection inputEyeDirection, IReadOnlyDictionary initPose) + private void Apply(LookAtEyeDirection inputEyeDirection) { // 1. Validate user input, and Output as actual weights. _validator.Validate(_inputWeights, _actualWeights, @@ -125,7 +129,7 @@ namespace UniVRM10 _eyeDirectionApplicable?.Apply(_actualEyeDirection, _actualWeights); // 3. Set actual weights to raw blendshapes. - _merger.SetValues(_actualWeights, initPose); + _merger.SetValues(_actualWeights); BlinkOverrideRate = blink; LookAtOverrideRate = lookAt; diff --git a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs index 4ed7d05d3..41729d9b3 100644 --- a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs +++ b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using UniGLTF; using UniGLTF.Extensions.VRMC_vrm; +using UniGLTF.Extensions.VRMC_vrm_expressions_node_transform; using UnityEngine; namespace UniVRM10 @@ -86,5 +87,29 @@ namespace UniVRM10 } return binding; } + + public static NodeTransformBinding? Build10(this NodeTransformBind bind, GameObject root, Vrm10Importer importer) + { + if (bind.Node.TryGetValidIndex(importer.Nodes.Count, out var nodeIndex)) + { + var node = importer.Nodes[nodeIndex]; + var relativePath = node.RelativePathFrom(root.transform); + + var t = bind.Translation != null && bind.Translation.Length >= 3 + ? new Vector3(bind.Translation[0], bind.Translation[1], bind.Translation[2]) + : Vector3.zero; + var r = bind.Rotation != null && bind.Rotation.Length >= 4 + ? new Quaternion(bind.Rotation[0], bind.Rotation[1], bind.Rotation[2], bind.Rotation[3]) + : Quaternion.identity; + var s = bind.Scale != null && bind.Scale.Length >= 3 + ? new Vector3(bind.Scale[0], bind.Scale[1], bind.Scale[2]) + : Vector3.zero; + return new NodeTransformBinding(relativePath, t, r, s); + } + else + { + return default; + } + } } } diff --git a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs index a0bf43867..32255cac1 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -779,6 +779,17 @@ namespace UniVRM10 }; } + static UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind ExportNodeTransformBinding(NodeTransformBinding binding, Func getIndex) + { + return new UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind + { + Node = getIndex(binding.RelativePath), + Translation = new float[] { binding.OffsetTranslation.x, binding.OffsetRotation.y, binding.OffsetTranslation.z }, + Rotation = new float[] { binding.OffsetRotation.x, binding.OffsetRotation.y, binding.OffsetRotation.z, binding.OffsetRotation.w }, + Scale = new float[] { binding.TargetScale.x, binding.TargetScale.y, binding.TargetScale.z }, + }; + } + static UniGLTF.Extensions.VRMC_vrm.Expression ExportExpression(VRM10Expression e, Vrm10Instance vrmController, Model model, ModelExporter converter) { if (e == null) @@ -786,7 +797,7 @@ namespace UniVRM10 return null; } - Func getIndexFromRelativePath = relativePath => + Func getRendererNodeIndexFromRelativePath = relativePath => { var rendererNode = vrmController.transform.GetFromPath(relativePath); var renderer = rendererNode.GetComponent(); @@ -831,7 +842,7 @@ namespace UniVRM10 { try { - var binding = ExportMorphTargetBinding(b, getIndexFromRelativePath); + var binding = ExportMorphTargetBinding(b, getRendererNodeIndexFromRelativePath); if (binding.Node < 0) { // node もしくは renderer が存在しない @@ -867,6 +878,43 @@ namespace UniVRM10 UniGLTFLogger.Warning($"{ex}"); } } + + if (e.NodeTransformBindings != null && e.NodeTransformBindings.Length > 0) + { + Func getNodeIndexFromRelativePath = relativePath => + { + var n = vrmController.transform.GetFromPath(relativePath); + var node = converter.Nodes[n.gameObject]; + return model.Nodes.IndexOf(node); + }; + + var nodeTransform = new UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.VRMC_vrm_expressions_node_transform + { + NodeTransformBinds = new(), + }; + foreach (var b in e.NodeTransformBindings) + { + try + { + var binding = ExportNodeTransformBinding(b, getNodeIndexFromRelativePath); + if (binding.Node < 0) + { + // node もしくは renderer が存在しない + continue; + } + + nodeTransform.NodeTransformBinds.Add(binding); + } + catch (Exception ex) + { + UniGLTFLogger.Warning($"{ex}"); + } + } + glTFExtension extensions = default; + UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.GltfSerializer.SerializeTo(ref extensions, nodeTransform); + vrmExpression.Extensions = extensions; + } + return vrmExpression; } diff --git a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs index e0541a57d..ae51eb0ae 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using UniGLTF; using UniGLTF.Extensions.VRMC_springBone_limit; using UniGLTF.Utils; +using UnityEditor.Experimental.GraphView; using UnityEngine; namespace UniVRM10 @@ -286,6 +287,25 @@ namespace UniVRM10 clip.MaterialUVBindings = new MaterialUVBinding[] { }; } + if (UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.GltfDeserializer.TryGet( + expression.Extensions as glTFExtension, + out UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.VRMC_vrm_expressions_node_transform nodeTransform)) + { + if (nodeTransform.NodeTransformBinds != null) + { + clip.NodeTransformBindings = nodeTransform.NodeTransformBinds? + .Select(x => x.Build10(Root, this)) + .Where(x => x.HasValue) + .Select(x => x.Value) + .ToArray(); + } + else + { + clip.NodeTransformBindings = new NodeTransformBinding[] { }; + } + + } + m_expressions.Add((preset, clip)); } return clip;