diff --git a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs index fc6724f5d..e285ec744 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -14,6 +14,7 @@ namespace UniVRM10 public const string SPRINGBONE_EXTENDED_COLLIDER_SPEC_VERSION = "1.0"; public const string NODE_CONSTRAINT_SPEC_VERSION = "1.0"; public const string MTOON_SPEC_VERSION = "1.0"; + public const string VRMC_springBone_limit_SPEC_VERSION = "1.0-draft"; public const string LICENSE_URL_JA = "https://vrm.dev/licenses/1.0/"; public const string LICENSE_URL_EN = "https://vrm.dev/licenses/1.0/en/"; @@ -451,63 +452,70 @@ namespace UniVRM10 GravityPower = y.m_gravityPower, }; - switch (y.m_anglelimitType) + if (y.transform.childCount > 0) { - case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone: - { - var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit + // childが存在する => 末端でない + switch (y.m_anglelimitType) + { + case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone: { - Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit + var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit { - Cone = new UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit + SpecVersion = VRMC_springBone_limit_SPEC_VERSION, + Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit { - Rotation = ReverseXToFloat4(y.m_limitSpaceOffset), - Angle = y.m_pitch, + Cone = new UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit + { + Rotation = ReverseXToFloat4(y.m_limitSpaceOffset), + Angle = y.m_pitch, + } } - } - }; - glTFExtension extensions = default; - UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit); - joint.Extensions = extensions; - break; - } - case UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge: - { - var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit + }; + glTFExtension extensions = default; + UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit); + joint.Extensions = extensions; + break; + } + case UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge: { - Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit + var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit { - Hinge = new UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit + SpecVersion = VRMC_springBone_limit_SPEC_VERSION, + Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit { - Rotation = ReverseXToFloat4(y.m_limitSpaceOffset), - Angle = y.m_pitch, + Hinge = new UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit + { + Rotation = ReverseXToFloat4(y.m_limitSpaceOffset), + Angle = y.m_pitch, + } } - } - }; - glTFExtension extensions = default; - UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit); - joint.Extensions = extensions; - break; - } - case UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical: - { - var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit + }; + glTFExtension extensions = default; + UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit); + joint.Extensions = extensions; + break; + } + case UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical: { - Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit + var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit { - Spherical = new UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit + SpecVersion = VRMC_springBone_limit_SPEC_VERSION, + Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit { - Rotation = ReverseXToFloat4(y.m_limitSpaceOffset), - Pitch = y.m_pitch, - Yaw = y.m_yaw, + Spherical = new UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit + { + Rotation = ReverseXToFloat4(y.m_limitSpaceOffset), + Pitch = y.m_pitch, + Yaw = y.m_yaw, + } } - } - }; - glTFExtension extensions = default; - UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit); - joint.Extensions = extensions; - break; - } + }; + glTFExtension extensions = default; + UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit); + joint.Extensions = extensions; + break; + } + } } return joint; } diff --git a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs index 698d11554..3293a901b 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs @@ -650,24 +650,27 @@ namespace UniVRM10 if (UniGLTF.Extensions.VRMC_springBone_limit.GltfDeserializer.TryGet(gltfJoint.Extensions as glTFExtension, out var extensionSpringBoneLimit)) { - if (extensionSpringBoneLimit.Limit.Cone is UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit cone) + if (VRMC_springBone_limit_Validate(go.transform, extensionSpringBoneLimit)) { - joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone; - joint.m_limitSpaceOffset = QuaternionFromFloat4(cone.Rotation); - joint.m_pitch = math.clamp(cone.Angle.GetValueOrDefault(), 0.0f, math.PI); - } - else if (extensionSpringBoneLimit.Limit.Hinge is UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit hinge) - { - joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge; - joint.m_limitSpaceOffset = QuaternionFromFloat4(hinge.Rotation); - joint.m_pitch = math.clamp(hinge.Angle.GetValueOrDefault(), 0.0f, math.PI); - } - else if (extensionSpringBoneLimit.Limit.Spherical is UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit spherical) - { - joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical; - joint.m_limitSpaceOffset = QuaternionFromFloat4(spherical.Rotation); - joint.m_pitch = math.clamp(spherical.Pitch.GetValueOrDefault(), 0.0f, math.PI); - joint.m_yaw = math.clamp(spherical.Yaw.GetValueOrDefault(), 0.0f, math.PI / 2); + if (extensionSpringBoneLimit.Limit.Cone is UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit cone) + { + joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone; + joint.m_limitSpaceOffset = QuaternionFromFloat4(cone.Rotation); + joint.m_pitch = cone.Angle.GetValueOrDefault(); + } + else if (extensionSpringBoneLimit.Limit.Hinge is UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit hinge) + { + joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge; + joint.m_limitSpaceOffset = QuaternionFromFloat4(hinge.Rotation); + joint.m_pitch = hinge.Angle.GetValueOrDefault(); + } + else if (extensionSpringBoneLimit.Limit.Spherical is UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit spherical) + { + joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical; + joint.m_limitSpaceOffset = QuaternionFromFloat4(spherical.Rotation); + joint.m_pitch = spherical.Pitch.GetValueOrDefault(); + joint.m_yaw = spherical.Yaw.GetValueOrDefault(); + } } } @@ -678,6 +681,60 @@ namespace UniVRM10 } } + private static bool VRMC_springBone_limit_Validate(Transform node, VRMC_springBone_limit extensionSpringBoneLimit) + { + var validate = true; + if (extensionSpringBoneLimit.SpecVersion != Vrm10Exporter.VRMC_springBone_limit_SPEC_VERSION) + { + validate = false; + UniGLTFLogger.Warning($"Unknown VRMC_springBone_limit specVersion: {extensionSpringBoneLimit.SpecVersion}"); + } + + var count = 0; + if (extensionSpringBoneLimit.Limit != null) + { + if (extensionSpringBoneLimit.Limit.Cone is UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit cone) + { + ++count; + } + if (extensionSpringBoneLimit.Limit.Hinge is UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit hinge) + { + ++count; + } + if (extensionSpringBoneLimit.Limit.Spherical is UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit spherical) + { + ++count; + } + } + + switch (count) + { + case 0: + // Limit 欠落 + validate = false; + UniGLTFLogger.Warning($"No VRMC_springBone_limit.Limit: skip"); + break; + + case 1: + // ok + break; + + default: + validate = false; + UniGLTFLogger.Warning($"Multiple VRMC_springBone_limit.Limit: skip"); + break; + } + + if (node.childCount == 0) + { + // leaf + UniGLTFLogger.Warning($"VRMC_springBone_limit: leaf node '{node.name}' must not have limit. skip"); + validate = false; + } + + return validate; + } + private static Quaternion QuaternionFromFloat4(float[] xyzw) { var q = (xyzw != null && xyzw.Length == 4)