Merge pull request #2722 from ousttrue/feature/springbonelimit_serializer

[SpringBoneLimit] Importer / Exporter
This commit is contained in:
ousttrue
2025-09-12 13:44:11 +09:00
committed by GitHub
10 changed files with 696 additions and 0 deletions

View File

@@ -86,6 +86,14 @@ namespace UniVRM10
$"{Vrm10SpecDir}/VRMC_springBone_extended_collider-1.0/schema/VRMC_springBone_extended_collider.schema.json",
$"{Vrm10FormatGeneratedDir}/SpringBoneExtendedCollider"
),
// VRMC_springBone_limit-1.0
// (experimental)
// https://github.com/0b5vr/vrm-specification/tree/springBone-limit
new GenerateInfo(
$"{Vrm10SpecDir}/VRMC_springBone_limit-1.0/schema/VRMC_springBone_limit.schema.json",
$"{Vrm10FormatGeneratedDir}/SpringBoneLimit"
),
};
foreach (var arg in args)

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6b6c07a37ce7d2348beb488d3c2fad01
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,236 @@
// 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_springBone_limit {
public static class GltfDeserializer
{
public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(VRMC_springBone_limit.ExtensionName);
public static bool TryGet(UniGLTF.glTFExtension src, out VRMC_springBone_limit 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_springBone_limit Deserialize(JsonNode parsed)
{
var value = new VRMC_springBone_limit();
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=="specVersion"){
value.SpecVersion = kv.Value.GetString();
continue;
}
if(key=="limit"){
value.Limit = Deserialize_Limit(kv.Value);
continue;
}
}
return value;
}
public static Limit Deserialize_Limit(JsonNode parsed)
{
var value = new Limit();
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=="cone"){
value.Cone = __limit_Deserialize_Cone(kv.Value);
continue;
}
if(key=="hinge"){
value.Hinge = __limit_Deserialize_Hinge(kv.Value);
continue;
}
if(key=="spherical"){
value.Spherical = __limit_Deserialize_Spherical(kv.Value);
continue;
}
}
return value;
}
public static ConeLimit __limit_Deserialize_Cone(JsonNode parsed)
{
var value = new ConeLimit();
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=="angle"){
value.Angle = kv.Value.GetSingle();
continue;
}
if(key=="rotation"){
value.Rotation = __limit__cone_Deserialize_Rotation(kv.Value);
continue;
}
}
return value;
}
public static float[] __limit__cone_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 HingeLimit __limit_Deserialize_Hinge(JsonNode parsed)
{
var value = new HingeLimit();
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=="angle"){
value.Angle = kv.Value.GetSingle();
continue;
}
if(key=="rotation"){
value.Rotation = __limit__hinge_Deserialize_Rotation(kv.Value);
continue;
}
}
return value;
}
public static float[] __limit__hinge_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 SphericalLimit __limit_Deserialize_Spherical(JsonNode parsed)
{
var value = new SphericalLimit();
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=="phi"){
value.Phi = kv.Value.GetSingle();
continue;
}
if(key=="theta"){
value.Theta = kv.Value.GetSingle();
continue;
}
if(key=="rotation"){
value.Rotation = __limit__spherical_Deserialize_Rotation(kv.Value);
continue;
}
}
return value;
}
public static float[] __limit__spherical_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;
}
} // GltfDeserializer
} // UniGLTF

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91feea663efc32c40bc07c67bdc8b868
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,91 @@
// This file is generated from JsonSchema. Don't modify this source code.
using System;
using System.Collections.Generic;
namespace UniGLTF.Extensions.VRMC_springBone_limit
{
public class ConeLimit
{
// Dictionary object with extension-specific objects.
public object Extensions;
// Application-specific data.
public object Extras;
// The angle of the cone limit in radians. If the angle is set to π or greater, the angle will be interpreted as π by the implementation. When the angle is set to π, the cone shape becomes a sphere.
public float? Angle;
// The rotation from the default orientation of the cone limit. The rotation is represented as a quaternion (x, y, z, w), where w is the scalar.
public float[] Rotation;
}
public class HingeLimit
{
// Dictionary object with extension-specific objects.
public object Extensions;
// Application-specific data.
public object Extras;
// The angle of the hinge limit in radians. If the angle is set to π or greater, the angle will be interpreted as π by the implementation. When the angle is set to π, the hinge shape becomes a full disc.
public float? Angle;
// The rotation from the default orientation of the hinge limit. The rotation is represented as a quaternion (x, y, z, w), where w is the scalar.
public float[] Rotation;
}
public class SphericalLimit
{
// Dictionary object with extension-specific objects.
public object Extensions;
// 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.
public float? Phi;
// 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.
public float? Theta;
// 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.
public float[] Rotation;
}
public class Limit
{
// Dictionary object with extension-specific objects.
public object Extensions;
// Application-specific data.
public object Extras;
// A limit that restricts the orientation of the spring in a cone shape.
public ConeLimit Cone;
// A limit that restricts the orientation of the spring in a hinge shape.
public HingeLimit Hinge;
// A limit that restricts the orientation of the spring in a spherical coordinate shape.
public SphericalLimit Spherical;
}
public class VRMC_springBone_limit
{
public const string ExtensionName = "VRMC_springBone_limit";
// Dictionary object with extension-specific objects.
public object Extensions;
// Application-specific data.
public object Extras;
// Specification version of VRMC_springBone_limit.
public string SpecVersion;
// Describes a limit apply to the spring. Either cone, hinge, or spherical must be present.
public Limit Limit;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40be47ec456e1cb46a3ce8a3ecb70910
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,218 @@
// 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_springBone_limit {
static public class GltfSerializer
{
public static void SerializeTo(ref UniGLTF.glTFExtension dst, VRMC_springBone_limit 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_springBone_limit.ExtensionName, f.GetStoreBytes());
}
public static void Serialize(JsonFormatter f, VRMC_springBone_limit 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(!string.IsNullOrEmpty(value.SpecVersion)){
f.Key("specVersion");
f.Value(value.SpecVersion);
}
if(value.Limit!=null){
f.Key("limit");
Serialize_Limit(f, value.Limit);
}
f.EndMap();
}
public static void Serialize_Limit(JsonFormatter f, Limit 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.Cone!=null){
f.Key("cone");
__limit_Serialize_Cone(f, value.Cone);
}
if(value.Hinge!=null){
f.Key("hinge");
__limit_Serialize_Hinge(f, value.Hinge);
}
if(value.Spherical!=null){
f.Key("spherical");
__limit_Serialize_Spherical(f, value.Spherical);
}
f.EndMap();
}
public static void __limit_Serialize_Cone(JsonFormatter f, ConeLimit 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.Angle.HasValue){
f.Key("angle");
f.Value(value.Angle.GetValueOrDefault());
}
if(value.Rotation!=null&&value.Rotation.Count()>=4){
f.Key("rotation");
__limit__cone_Serialize_Rotation(f, value.Rotation);
}
f.EndMap();
}
public static void __limit__cone_Serialize_Rotation(JsonFormatter f, float[] value)
{
f.BeginList();
foreach(var item in value)
{
f.Value(item);
}
f.EndList();
}
public static void __limit_Serialize_Hinge(JsonFormatter f, HingeLimit 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.Angle.HasValue){
f.Key("angle");
f.Value(value.Angle.GetValueOrDefault());
}
if(value.Rotation!=null&&value.Rotation.Count()>=4){
f.Key("rotation");
__limit__hinge_Serialize_Rotation(f, value.Rotation);
}
f.EndMap();
}
public static void __limit__hinge_Serialize_Rotation(JsonFormatter f, float[] value)
{
f.BeginList();
foreach(var item in value)
{
f.Value(item);
}
f.EndList();
}
public static void __limit_Serialize_Spherical(JsonFormatter f, SphericalLimit 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.Phi.HasValue){
f.Key("phi");
f.Value(value.Phi.GetValueOrDefault());
}
if(value.Theta.HasValue){
f.Key("theta");
f.Value(value.Theta.GetValueOrDefault());
}
if(value.Rotation!=null&&value.Rotation.Count()>=4){
f.Key("rotation");
__limit__spherical_Serialize_Rotation(f, value.Rotation);
}
f.EndMap();
}
public static void __limit__spherical_Serialize_Rotation(JsonFormatter f, float[] value)
{
f.BeginList();
foreach(var item in value)
{
f.Value(item);
}
f.EndList();
}
} // class
} // namespace

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02602b2f45f722f47a872ca0d1d8262d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -140,6 +140,12 @@ namespace UniVRM10
return new float[] { -v.x, v.y, v.z };
}
static float[] ReverseX(Quaternion q)
{
q = UniGLTF.Axes.X.Create().InvertQuaternion(q);
return new float[] { q.x, q.y, q.z, q.w };
}
///
/// 必要な容量を計算
/// (sparseは考慮してないので大きめ)
@@ -444,6 +450,65 @@ namespace UniVRM10
GravityDir = ReverseX(y.m_gravityDir),
GravityPower = y.m_gravityPower,
};
switch (y.m_anglelimitType)
{
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone:
{
var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit
{
Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit
{
Cone = new UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit
{
Rotation = ReverseX(y.m_angleLimitRotation),
Angle = y.m_angleLimitAngle1,
}
}
};
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
{
Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit
{
Hinge = new UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit
{
Rotation = ReverseX(y.m_angleLimitRotation),
Angle = y.m_angleLimitAngle1,
}
}
};
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
{
Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit
{
Spherical = new UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit
{
Rotation = ReverseX(y.m_angleLimitRotation),
Theta = y.m_angleLimitAngle1,
Phi = y.m_angleLimitAngle2,
}
}
};
glTFExtension extensions = default;
UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit);
joint.Extensions = extensions;
break;
}
}
return joint;
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using UniGLTF;
using UniGLTF.Extensions.VRMC_springBone_limit;
using UniGLTF.Utils;
using UnityEngine;
@@ -626,6 +628,31 @@ namespace UniVRM10
joint.m_gravityDir = gltfJoint.GravityDir != null ? Vector3InvertX(gltfJoint.GravityDir) : Vector3.down;
joint.m_gravityPower = gltfJoint.GravityPower.GetValueOrDefault(0.0f);
joint.m_stiffnessForce = gltfJoint.Stiffness.GetValueOrDefault(1.0f);
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)
{
joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone;
joint.m_angleLimitRotation = QuaternionFromFloat4(cone.Rotation);
joint.m_angleLimitAngle1 = cone.Angle.GetValueOrDefault(Mathf.PI * 0.5f);
}
else if (extensionSpringBoneLimit.Limit.Hinge is UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit hinge)
{
joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge;
joint.m_angleLimitRotation = QuaternionFromFloat4(hinge.Rotation);
joint.m_angleLimitAngle1 = hinge.Angle.GetValueOrDefault(Mathf.PI * 0.5f);
}
else if (extensionSpringBoneLimit.Limit.Spherical is UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit spherical)
{
joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical;
joint.m_angleLimitRotation = QuaternionFromFloat4(spherical.Rotation);
joint.m_angleLimitAngle1 = spherical.Theta.GetValueOrDefault(Mathf.PI * 0.5f);
joint.m_angleLimitAngle2 = spherical.Phi.GetValueOrDefault(Mathf.PI * 0.5f);
}
}
spring.Joints.Add(joint);
}
}
@@ -633,6 +660,16 @@ namespace UniVRM10
}
}
private static Quaternion QuaternionFromFloat4(float[] xyzw)
{
var q = (xyzw != null && xyzw.Length == 4)
? new Quaternion(xyzw[0], xyzw[1], xyzw[2], xyzw[3])
: Quaternion.identity
;
// vrm-1.0 is x inverted
return Axes.X.Create().InvertQuaternion(q);
}
static AxisMask ConstraintAxes(bool[] flags)
{
var mask = default(AxisMask);