Merge pull request #2833 from ousttrue/fix/spring_limit_specversion

[VRMC_springBone_limit] specVersion and validation
This commit is contained in:
ousttrue
2026-09-25 10:31:25 +09:00
committed by GitHub
2 changed files with 126 additions and 61 deletions

View File

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

View File

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