diff --git a/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs b/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs index 4791a460ab..bc23218ead 100644 --- a/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs +++ b/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs @@ -99,6 +99,21 @@ public static bool UVProp(Rect rect, SerializedProperty prop) } } + 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 4020fcf06b..db49a99c01 100644 --- a/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs +++ b/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs @@ -110,6 +110,10 @@ public Texture Render(Rect r, GUIStyle background, PreviewSceneManager scene, 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 0000000000..55dcf27f30 --- /dev/null +++ b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs @@ -0,0 +1,133 @@ +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); + EditorGUI.BeginChangeCheck(); + EditorGUI.PropertyField(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.OffsetRotation))); + if (EditorGUI.EndChangeCheck()) + { + 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/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta new file mode 100644 index 0000000000..3c72f298bd --- /dev/null +++ b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 186e6dd105c86904497f3ac0dfd61212 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs b/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs index 782d355a34..768cc15f07 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 @@ public class SerializedExpressionEditor 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 @@ public SerializedExpressionEditor(SerializedObject serializedObject, VRM10Expres 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 @@ public bool Draw(out VRM10Expression bakeValue) 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(s_Mode.ToString())) + { + m_changed = true; + } + } + break; + + case ListMode.MaterialColor: { - if (m_morphTargetBindings.Draw("MorphTarget")) + if (m_materialColorBindings.Draw(s_Mode.ToString())) { m_changed = true; } } break; - case 1: - // Material + case ListMode.TextureTransform: { - if (m_materialColorBindings.Draw("MaterialColor")) + if (m_materialUVBindings.Draw(s_Mode.ToString())) { m_changed = true; } } break; - case 2: - // TextureTransform + case ListMode.NodeTransform: { - if (m_materialUVBindings.Draw("TextureTransform")) + if (m_nodeTransformBindings.Draw(s_Mode.ToString())) { m_changed = true; } diff --git a/Packages/VRM10/Editor/Vrm10SerializerGenerator.cs b/Packages/VRM10/Editor/Vrm10SerializerGenerator.cs index ec5b753d95..cd3b24015b 100644 --- a/Packages/VRM10/Editor/Vrm10SerializerGenerator.cs +++ b/Packages/VRM10/Editor/Vrm10SerializerGenerator.cs @@ -36,6 +36,7 @@ public GenerateInfo(string jsonSchema, string formatDir) : this(jsonSchema, form private const string Vrm10SpecDir = "vrm-specification/specification"; private const string Vrm10FormatGeneratedDir = "Packages/VRM10/Runtime/Format"; + private const string UniGltfFormatGeneratedDir = "Packages/UniGLTF/Runtime/UniGLTF/Format"; public static void Run(bool debug) { @@ -48,7 +49,7 @@ public static void Run(bool debug) // VRMC_hdr_emissiveMultiplier new GenerateInfo( $"{Vrm10SpecDir}/VRMC_materials_hdr_emissiveMultiplier-1.0/schema/VRMC_materials_hdr_emissiveMultiplier.json", - "Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/EmissiveMultiplier" + $"{UniGltfFormatGeneratedDir}/ExtensionsAndExtras/EmissiveMultiplier" ), // VRMC_vrm @@ -94,6 +95,14 @@ public static void Run(bool debug) $"{Vrm10SpecDir}/VRMC_springBone_limit-1.0/schema/VRMC_springBone_limit.schema.json", $"{Vrm10FormatGeneratedDir}/SpringBoneLimit" ), + + // VRMC_vrm_expressions_node_transform-1.0 + // (experimental) + // https://github.com/ousttrue/vrm-specification/tree/VRMC_vrm_expression_joint + new GenerateInfo( + $"{Vrm10SpecDir}/VRMC_vrm_expressions_node_transform-1.0/schema/VRMC_vrm_expressions_node_transform.schema.json", + $"{Vrm10FormatGeneratedDir}/ExpressionsNodeTransform" + ), }; foreach (var arg in args) diff --git a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs index dd54cb70d9..de9a6a55f0 100644 --- a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs +++ b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using UniGLTF.Utils; using UnityEngine; namespace UniVRM10 @@ -23,9 +24,13 @@ internal sealed class ExpressionMerger : IDisposable MorphTargetBindingMerger m_morphTargetBindingMerger; MaterialValueBindingMerger m_materialValueBindingMerger; + 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), @@ -35,6 +40,7 @@ public ExpressionMerger(VRM10ObjectExpression expressions, Transform root, bool m_valueMap = new Dictionary(ExpressionKey.Comparer); m_morphTargetBindingMerger = new MorphTargetBindingMerger(m_clipMap, root); m_materialValueBindingMerger = new MaterialValueBindingMerger(m_clipMap, root, isPrefabInstance); + m_boneTransformBindingMerger = new NodeTransformBindingMerger(m_clipMap, root, initPose); } /// @@ -50,6 +56,7 @@ public void SetValues(Dictionary expressionWeights) m_morphTargetBindingMerger.Apply(); m_materialValueBindingMerger.Apply(); + m_boneTransformBindingMerger.Apply(); } private void AccumulateValue(ExpressionKey key, float value) @@ -69,6 +76,7 @@ private void AccumulateValue(ExpressionKey key, float value) m_morphTargetBindingMerger.AccumulateValue(key, value); m_materialValueBindingMerger.AccumulateValue(clip, value); + m_boneTransformBindingMerger.AccumulateValue(key, value); } public void Dispose() diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.cs b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.cs new file mode 100644 index 0000000000..a6ecff11fd --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.cs @@ -0,0 +1,56 @@ +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) + ); + if (this.TargetScale != Vector3.zero) + { + node.localScale = Vector3.Lerp(init.LocalScale, this.TargetScale, weight); + } + } + } +} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.cs.meta b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.cs.meta new file mode 100644 index 0000000000..bb06db17fb --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.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/NodeTransformBindingMerger.cs b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs new file mode 100644 index 0000000000..c972a8437e --- /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/NodeTransformBindingMerger.cs.meta b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs.meta new file mode 100644 index 0000000000..1e48344b2f --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe2bf5e71647b0f4d93ca6c22bb9fb7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewMeshItem.cs b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewMeshItem.cs index 2765bd2549..5d7a6a28b3 100644 --- a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewMeshItem.cs +++ b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewMeshItem.cs @@ -123,7 +123,9 @@ public static PreviewMeshItem Create(Transform t, Transform root, { return new PreviewMeshItem(t.RelativePathFrom(root), t, skinnedMeshRenderer.sharedMaterials) { - Mesh = skinnedMeshRenderer.sharedMesh, + SkinnedMeshRenderer = skinnedMeshRenderer, + Mesh = new Mesh(), // for bake + BlendShapeNames = new string[]{}, }; } } diff --git a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs index c935043449..30bdbe5a23 100644 --- a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs +++ b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs @@ -3,6 +3,7 @@ using UnityEngine; using System; using UniGLTF; +using UniGLTF.Utils; namespace UniVRM10 @@ -16,6 +17,8 @@ public sealed class PreviewSceneManager : MonoBehaviour public bool hasError; + private IReadOnlyDictionary m_defaultTransformStates; + #if UNITY_EDITOR public static PreviewSceneManager GetOrCreate(GameObject prefab) { @@ -85,7 +88,7 @@ public void Clean() private void Initialize(GameObject prefab) { hasError = false; - + Prefab = prefab; var materialNames = new List(); @@ -104,14 +107,14 @@ private void Initialize(GameObject prefab) { 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 @@ private void Initialize(GameObject prefab) 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 @@ private void Initialize(GameObject prefab) .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 @@ public string[] SkinnedMeshRendererPathList 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 @@ public void Bake(VRM10Expression bake, float weight) 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 b75473e245..d0fd471d3d 100644 --- a/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs +++ b/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs @@ -46,5 +46,11 @@ public sealed class VRM10Expression : PrefabRelatedScriptableObject /// [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/Vrm10Runtime/Vrm10Runtime.cs b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs index e702496b1c..e229563041 100644 --- a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs +++ b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs @@ -77,7 +77,7 @@ public Vrm10Runtime(Vrm10Instance instance, bool useControlRig, IVrm10SpringBone } 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; } diff --git a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs index 0a38b04811..3c13e449f7 100644 --- a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs +++ b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using UniGLTF.Utils; +using UnityEngine; namespace UniVRM10 { @@ -23,9 +25,13 @@ public sealed class Vrm10RuntimeExpression : IDisposable 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(); diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform.meta b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform.meta new file mode 100644 index 0000000000..e638340529 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d80842db26fe76645bf195c10b9c6bf7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs new file mode 100644 index 0000000000..dfa8a34697 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs @@ -0,0 +1,145 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using UniJSON; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF.Extensions.VRMC_vrm_expressions_node_transform { + +public static class GltfDeserializer +{ + public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(VRMC_vrm_expressions_node_transform.ExtensionName); + +public static bool TryGet(UniGLTF.glTFExtension src, out VRMC_vrm_expressions_node_transform extension) +{ + if(src is UniGLTF.glTFExtensionImport extensions) + { + foreach(var kv in extensions.ObjectItems()) + { + if(kv.Key.GetUtf8String() == ExtensionNameUtf8) + { + extension = Deserialize(kv.Value); + return true; + } + } + } + + extension = default; + return false; +} + + +public static VRMC_vrm_expressions_node_transform Deserialize(JsonNode parsed) +{ + var value = new VRMC_vrm_expressions_node_transform(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="nodeTransformBinds"){ + value.NodeTransformBinds = Deserialize_NodeTransformBinds(kv.Value); + continue; + } + + } + return value; +} + +public static List Deserialize_NodeTransformBinds(JsonNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_NodeTransformBinds_ITEM(x)); + } + return value; +} + +public static NodeTransformBind Deserialize_NodeTransformBinds_ITEM(JsonNode parsed) +{ + var value = new NodeTransformBind(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + if(key=="rotation"){ + value.Rotation = __nodeTransformBinds_ITEM_Deserialize_Rotation(kv.Value); + continue; + } + + if(key=="scale"){ + value.Scale = __nodeTransformBinds_ITEM_Deserialize_Scale(kv.Value); + continue; + } + + if(key=="translation"){ + value.Translation = __nodeTransformBinds_ITEM_Deserialize_Translation(kv.Value); + continue; + } + + } + return value; +} + +public static float[] __nodeTransformBinds_ITEM_Deserialize_Rotation(JsonNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static float[] __nodeTransformBinds_ITEM_Deserialize_Scale(JsonNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static float[] __nodeTransformBinds_ITEM_Deserialize_Translation(JsonNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +} // GltfDeserializer +} // UniGLTF diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs.meta b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs.meta new file mode 100644 index 0000000000..4d946ab7e5 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a0544899292fe54e961d98e2f7d755c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs new file mode 100644 index 0000000000..48421d4b1c --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs @@ -0,0 +1,43 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; + + +namespace UniGLTF.Extensions.VRMC_vrm_expressions_node_transform +{ + + public class NodeTransformBind + { + // Dictionary object with extension-specific objects. + public object Extensions; + + // Application-specific data. + public object Extras; + + // The node index. + public int? Node; + + // The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar. + public float[] Rotation; + + // The node's non-uniform scale, given as the scaling factors along the x, y, and z axes. + public float[] Scale; + + // The node's translation along the x, y, and z axes. + public float[] Translation; + } + + public class VRMC_vrm_expressions_node_transform + { + public const string ExtensionName = "VRMC_vrm_expressions_node_transform"; + + // Dictionary object with extension-specific objects. + public object Extensions; + + // Application-specific data. + public object Extras; + + // Specify a node transform + public List NodeTransformBinds; + } +} diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs.meta b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs.meta new file mode 100644 index 0000000000..311817f130 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f6d0369358084249b383bea969b16a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs new file mode 100644 index 0000000000..a3f38a515d --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs @@ -0,0 +1,141 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using System.Linq; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_vrm_expressions_node_transform { + + static public class GltfSerializer + { + + public static void SerializeTo(ref UniGLTF.glTFExtension dst, VRMC_vrm_expressions_node_transform extension) + { + if (dst is glTFExtensionImport) + { + throw new NotImplementedException(); + } + + if (!(dst is glTFExtensionExport extensions)) + { + extensions = new glTFExtensionExport(); + dst = extensions; + } + + var f = new JsonFormatter(); + Serialize(f, extension); + extensions.Add(VRMC_vrm_expressions_node_transform.ExtensionName, f.GetStoreBytes()); + } + + +public static void Serialize(JsonFormatter f, VRMC_vrm_expressions_node_transform value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + (value.Extensions as glTFExtension).Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + (value.Extras as glTFExtension).Serialize(f); + } + + if(value.NodeTransformBinds!=null&&value.NodeTransformBinds.Count()>=1){ + f.Key("nodeTransformBinds"); + Serialize_NodeTransformBinds(f, value.NodeTransformBinds); + } + + f.EndMap(); +} + +public static void Serialize_NodeTransformBinds(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_NodeTransformBinds_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_NodeTransformBinds_ITEM(JsonFormatter f, NodeTransformBind value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + (value.Extensions as glTFExtension).Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + (value.Extras as glTFExtension).Serialize(f); + } + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + if(value.Rotation!=null&&value.Rotation.Count()>=4){ + f.Key("rotation"); + __nodeTransformBinds_ITEM_Serialize_Rotation(f, value.Rotation); + } + + if(value.Scale!=null&&value.Scale.Count()>=3){ + f.Key("scale"); + __nodeTransformBinds_ITEM_Serialize_Scale(f, value.Scale); + } + + if(value.Translation!=null&&value.Translation.Count()>=3){ + f.Key("translation"); + __nodeTransformBinds_ITEM_Serialize_Translation(f, value.Translation); + } + + f.EndMap(); +} + +public static void __nodeTransformBinds_ITEM_Serialize_Rotation(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void __nodeTransformBinds_ITEM_Serialize_Scale(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void __nodeTransformBinds_ITEM_Serialize_Translation(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + + } // class +} // namespace diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs.meta b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs.meta new file mode 100644 index 0000000000..773237b4d6 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16a49328d069d9f47840c104597af4a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Format/SpringBoneLimit/Format.g.cs b/Packages/VRM10/Runtime/Format/SpringBoneLimit/Format.g.cs index f820abb772..655df84f10 100644 --- a/Packages/VRM10/Runtime/Format/SpringBoneLimit/Format.g.cs +++ b/Packages/VRM10/Runtime/Format/SpringBoneLimit/Format.g.cs @@ -44,10 +44,10 @@ public class SphericalLimit // Application-specific data. public object Extras; - // The phi angle of the spherical limit in radians. If the phi angle is set to π or greater, the angle will be interpreted as π by the implementation. + // The pitch angle of the spherical limit in radians. If the pitch angle is set to π or greater, the angle will be interpreted as π by the implementation. public float? Pitch; - // The theta angle of the spherical limit in radians. If the theta angle is set to π/2 or greater, the angle will be interpreted as π/2 by the implementation. + // The yaw angle of the spherical limit in radians. If the yaw angle is set to π/2 or greater, the angle will be interpreted as π/2 by the implementation. public float? Yaw; // The rotation from the default orientation of the spherical limit. The rotation is represented as a quaternion (x, y, z, w), where w is the scalar. diff --git a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs index 4ed7d05d3c..8120c52d11 100644 --- a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs +++ b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs @@ -2,6 +2,7 @@ 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 @@ public static class ExpressionExtensions } 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]) + : node.transform.localScale; + 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 a0bf438673..ae243a2767 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -779,6 +779,19 @@ static UniGLTF.Extensions.VRMC_vrm.TextureTransformBind ExportTextureTransformBi }; } + static UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind ExportNodeTransformBinding(NodeTransformBinding binding, Func getIndex) + { + var translation = ReverseX(binding.OffsetTranslation); + var rotation = ReverseX(binding.OffsetRotation); + return new UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind + { + Node = getIndex(binding.RelativePath), + Translation = new float[] { translation.x, translation.y, translation.z }, + Rotation = new float[] { rotation.x, rotation.y, rotation.z, rotation.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 +799,7 @@ static UniGLTF.Extensions.VRMC_vrm.Expression ExportExpression(VRM10Expression e return null; } - Func getIndexFromRelativePath = relativePath => + Func getRendererNodeIndexFromRelativePath = relativePath => { var rendererNode = vrmController.transform.GetFromPath(relativePath); var renderer = rendererNode.GetComponent(); @@ -831,7 +844,7 @@ static UniGLTF.Extensions.VRMC_vrm.Expression ExportExpression(VRM10Expression e { try { - var binding = ExportMorphTargetBinding(b, getIndexFromRelativePath); + var binding = ExportMorphTargetBinding(b, getRendererNodeIndexFromRelativePath); if (binding.Node < 0) { // node もしくは renderer が存在しない @@ -867,6 +880,43 @@ static UniGLTF.Extensions.VRMC_vrm.Expression ExportExpression(VRM10Expression e 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 e0541a57d3..557b51b7d4 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs @@ -286,6 +286,25 @@ VRM10Expression GetOrLoadExpression(in SubAssetKey key, ExpressionPreset preset, 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;