diff --git a/Assets/VRM10/Editor/Vrm10SerializerGenerator.cs b/Assets/VRM10/Editor/Vrm10SerializerGenerator.cs index f22a16105..184132e93 100644 --- a/Assets/VRM10/Editor/Vrm10SerializerGenerator.cs +++ b/Assets/VRM10/Editor/Vrm10SerializerGenerator.cs @@ -79,6 +79,12 @@ namespace UniVRM10 $"{SPEC_DIR}/VRMC_vrm_animation-1.0/schema/VRMC_vrm_animation.schema.json", "Assets/VRM10/Runtime/Format/Animation" ), + + // VRMC_animation + new GenerateInfo( + $"{SPEC_DIR}/VRMC_springBone_extended_collider-1.0/schema/VRMC_springBone_extended_collider.schema.json", + "Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider" + ), }; foreach (var arg in args) diff --git a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneCollider.cs b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneCollider.cs index 60e3c45de..6c29a1270 100644 --- a/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneCollider.cs +++ b/Assets/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneCollider.cs @@ -7,6 +7,9 @@ namespace UniVRM10 { Sphere, Capsule, + Plane, + SphereInside, + CapsuleInside, } [Serializable] @@ -23,6 +26,10 @@ namespace UniVRM10 /// bone local position public Vector3 Tail; + public Vector3 Normal = Vector3.up; + + public Vector3 TailOrNormal => ColliderType == VRM10SpringBoneColliderTypes.Plane ? Normal : Tail; + public static int SelectedGuid; public bool IsSelected => GetInstanceID() == SelectedGuid; @@ -38,11 +45,107 @@ namespace UniVRM10 break; case VRM10SpringBoneColliderTypes.Capsule: - Gizmos.color = new Color(1.0f, 0.1f, 0.1f); - Gizmos.DrawWireSphere(Offset, Radius); - Gizmos.DrawWireSphere(Tail, Radius); - Gizmos.DrawLine(Offset, Tail); + Gizmos.color = Color.magenta; + DrawCapsuleGizmo(transform.localToWorldMatrix.MultiplyPoint(Offset), transform.localToWorldMatrix.MultiplyPoint(Tail), Radius); break; + + case VRM10SpringBoneColliderTypes.Plane: + Gizmos.color = new Color(1.0f, 0.5f, 0.0f); + DrawPlane(transform.localToWorldMatrix, Offset, Normal, Radius); + break; + + case VRM10SpringBoneColliderTypes.SphereInside: + Gizmos.color = new Color(0.5f, 0, 1.0f); + Gizmos.DrawWireSphere(Offset, Radius); + break; + + case VRM10SpringBoneColliderTypes.CapsuleInside: + Gizmos.color = new Color(0.5f, 0, 1.0f); + DrawCapsuleGizmo(transform.localToWorldMatrix.MultiplyPoint(Offset), transform.localToWorldMatrix.MultiplyPoint(Tail), Radius); + break; + + default: + throw new NotImplementedException(); + } + } + + public static void DrawCapsuleGizmo(Vector3 start, Vector3 end, float radius) + { + var tail = end - start; + var distance = (end - start).magnitude; + Gizmos.matrix = Matrix4x4.TRS(start, Quaternion.FromToRotation(Vector3.forward, tail), Vector3.one); + Gizmos.DrawWireSphere(Vector3.zero, radius); + Gizmos.DrawWireSphere(Vector3.forward * distance, radius); + var capsuleEnd = Vector3.forward * distance; + var offsets = new Vector3[] { new Vector3(-1.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), new Vector3(1.0f, 0.0f, 0.0f), new Vector3(0.0f, -1.0f, 0.0f) }; + for (int i = 0; i < offsets.Length; i++) + { + Gizmos.DrawLine(offsets[i] * radius, capsuleEnd + offsets[i] * radius); + } + Gizmos.matrix = Matrix4x4.identity; + } + + public static void DrawPlane(in Matrix4x4 m, in Vector3 offset, in Vector3 _n, float radius) + { + var n = _n.normalized; + Gizmos.matrix = m; + Gizmos.DrawLine(offset, offset + n * radius); + + // N Y-Axis として + // XZ 平面を描画する。 + var z = Vector3.Cross(Vector3.right, n); + if (z.magnitude > 1e-4) + { + var zr = z * radius; + var x = Vector3.Cross(n, z); + var xr = x * radius; + Gizmos.DrawLine(-xr, xr); + { + var o = offset + zr; + Gizmos.DrawLine(o - xr, o + xr); + } + { + var o = offset - zr; + Gizmos.DrawLine(o - xr, o + xr); + } + + Gizmos.DrawLine(-zr, zr); + { + var o = offset + xr; + Gizmos.DrawLine(o - zr, o + zr); + } + { + var o = offset - xr; + Gizmos.DrawLine(o - zr, o + zr); + } + } + else + { + // N と ローカル X-Axis が重なっていた場合 + var x = Vector3.Cross(n, Vector3.forward); + var xr = x * radius; + var _z = Vector3.Cross(x, n); + var zr = _z * radius; + + Gizmos.DrawLine(-xr, xr); + { + var o = offset + zr; + Gizmos.DrawLine(o - xr, o + xr); + } + { + var o = offset - zr; + Gizmos.DrawLine(o - xr, o + xr); + } + + Gizmos.DrawLine(-zr, zr); + { + var o = offset + xr; + Gizmos.DrawLine(o - zr, o + zr); + } + { + var o = offset - xr; + Gizmos.DrawLine(o - zr, o + zr); + } } } diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs index 8a40ad9e5..52eccc274 100644 --- a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs @@ -131,7 +131,7 @@ namespace UniVRM10 { offset = collider.Offset, radius = collider.Radius, - tail = collider.Tail, + tailOrNormal = collider.TailOrNormal, colliderType = TranslateColliderType(collider.ColliderType) } }).ToArray(), @@ -172,6 +172,12 @@ namespace UniVRM10 return BlittableColliderType.Sphere; case VRM10SpringBoneColliderTypes.Capsule: return BlittableColliderType.Capsule; + case VRM10SpringBoneColliderTypes.Plane: + return BlittableColliderType.Plane; + case VRM10SpringBoneColliderTypes.SphereInside: + return BlittableColliderType.SphereInside; + case VRM10SpringBoneColliderTypes.CapsuleInside: + return BlittableColliderType.CapsuleInside; default: throw new ArgumentOutOfRangeException(); } diff --git a/Assets/VRM10/Runtime/FastSpringBone/Blittables/BlittableCollider.cs b/Assets/VRM10/Runtime/FastSpringBone/Blittables/BlittableCollider.cs index 334887503..1f88e7fee 100644 --- a/Assets/VRM10/Runtime/FastSpringBone/Blittables/BlittableCollider.cs +++ b/Assets/VRM10/Runtime/FastSpringBone/Blittables/BlittableCollider.cs @@ -9,11 +9,12 @@ namespace UniVRM10.FastSpringBones.Blittables /// [Serializable] public struct BlittableCollider - { + { public BlittableColliderType colliderType; public Vector3 offset; public float radius; - public Vector3 tail; + // capsule tail or plane normal + public Vector3 tailOrNormal; public int transformIndex; } } \ No newline at end of file diff --git a/Assets/VRM10/Runtime/FastSpringBone/Blittables/BlittableColliderType.cs b/Assets/VRM10/Runtime/FastSpringBone/Blittables/BlittableColliderType.cs index 04f0b9f25..69ea0f91d 100644 --- a/Assets/VRM10/Runtime/FastSpringBone/Blittables/BlittableColliderType.cs +++ b/Assets/VRM10/Runtime/FastSpringBone/Blittables/BlittableColliderType.cs @@ -4,5 +4,8 @@ namespace UniVRM10.FastSpringBones.Blittables { Sphere, Capsule, + Plane, + SphereInside, + CapsuleInside, } } \ No newline at end of file diff --git a/Assets/VRM10/Runtime/FastSpringBone/System/UpdateFastSpringBoneJob.cs b/Assets/VRM10/Runtime/FastSpringBone/System/UpdateFastSpringBoneJob.cs index 87e5e16d4..4c7009d09 100644 --- a/Assets/VRM10/Runtime/FastSpringBone/System/UpdateFastSpringBoneJob.cs +++ b/Assets/VRM10/Runtime/FastSpringBone/System/UpdateFastSpringBoneJob.cs @@ -22,7 +22,7 @@ namespace UniVRM10.FastSpringBones.System [NativeDisableParallelForRestriction] public NativeArray Transforms; public float DeltaTime; - + public unsafe void Execute(int index) { var spring = Springs[index]; @@ -79,16 +79,32 @@ namespace UniVRM10.FastSpringBones.System var colliderScale = colliderTransform.localToWorldMatrix.lossyScale; var maxColliderScale = Mathf.Max(Mathf.Max(Mathf.Abs(colliderScale.x), Mathf.Abs(colliderScale.y)), Mathf.Abs(colliderScale.z)); var worldPosition = colliderTransform.localToWorldMatrix.MultiplyPoint3x4(collider.offset); - var worldTail = colliderTransform.localToWorldMatrix.MultiplyPoint3x4(collider.tail); - + var worldTail = colliderTransform.localToWorldMatrix.MultiplyPoint3x4(collider.tailOrNormal); + switch (collider.colliderType) { case BlittableColliderType.Sphere: - ResolveSphereCollision(joint, collider, worldPosition, headTransform, maxColliderScale, logic, ref nextTail); + ResolveSphereCollision(joint, collider, worldPosition, headTransform, maxColliderScale, logic, ref nextTail); break; + case BlittableColliderType.Capsule: ResolveCapsuleCollision(worldTail, worldPosition, headTransform, joint, collider, maxColliderScale, logic, ref nextTail); break; + + case BlittableColliderType.Plane: + ResolvePlaneCollision(joint, collider, colliderTransform, ref nextTail); + break; + + case BlittableColliderType.SphereInside: + ResolveSphereCollisionInside(joint, collider, colliderTransform, ref nextTail); + break; + + case BlittableColliderType.CapsuleInside: + ResolveCapsuleCollisionInside(joint, collider, colliderTransform, ref nextTail); + break; + + default: + throw new NotImplementedException(); } } @@ -203,5 +219,95 @@ namespace UniVRM10.FastSpringBones.System nextTail = headTransform.position + (posFromCollider - headTransform.position).normalized * logic.length; } } + + private static void ResolveSphereCollisionInside( + BlittableJoint joint, + BlittableCollider collider, + BlittableTransform colliderTransform, + ref Vector3 nextTail) + { + var transformedOffset = colliderTransform.localToWorldMatrix.MultiplyPoint(collider.offset); + var delta = nextTail - transformedOffset; + + // ジョイントとコライダーの距離。負の値は衝突していることを示す + var distance = collider.radius - joint.radius - delta.magnitude; + + // ジョイントとコライダーの距離の方向。衝突している場合、この方向にジョイントを押し出す + if (distance < 0) + { + var direction = -delta.normalized; + nextTail -= direction * distance; + } + } + + private static void ResolveCapsuleCollisionInside( + BlittableJoint joint, + BlittableCollider collider, + BlittableTransform colliderTransform, + ref Vector3 nextTail) + { + var transformedOffset = colliderTransform.localToWorldMatrix.MultiplyPoint(collider.offset); + var transformedTail = colliderTransform.localToWorldMatrix.MultiplyPoint(collider.tailOrNormal); + var offsetToTail = transformedTail - transformedOffset; + var lengthSqCapsule = offsetToTail.sqrMagnitude; + + var delta = nextTail - transformedOffset; + var dot = Vector3.Dot(offsetToTail, delta); + + if (dot < 0.0) + { + // ジョイントがカプセルの始点側にある場合 + // なにもしない + } + else if (dot > lengthSqCapsule) + { + // ジョイントがカプセルの終点側にある場合 + delta -= offsetToTail; + } + else + { + // ジョイントがカプセルの始点と終点の間にある場合 + delta -= offsetToTail * (dot / lengthSqCapsule); + } + + // ジョイントとコライダーの距離。負の値は衝突していることを示す + var distance = collider.radius - joint.radius - delta.magnitude; + + // ジョイントとコライダーの距離の方向。衝突している場合、この方向にジョイントを押し出す + if (distance < 0) + { + var direction = -delta.normalized; + nextTail -= direction * distance; + } + } + + /// + /// Collision with SpringJoint and PlaneCollider. + /// If collide update nextTail. + /// + /// joint + /// collier + /// colliderTransform.localToWorldMatrix.MultiplyPoint3x4(collider.offset); + /// result of verlet integration + private static void ResolvePlaneCollision( + BlittableJoint joint, + BlittableCollider collider, + BlittableTransform colliderTransform, + ref Vector3 nextTail) + { + var transformedOffset = colliderTransform.localToWorldMatrix.MultiplyPoint(collider.offset); + var transformedNormal = colliderTransform.localToWorldMatrix.MultiplyVector(collider.tailOrNormal).normalized; + var delta = nextTail - transformedOffset; + + // ジョイントとコライダーの距離。負の値は衝突していることを示す + var distance = Vector3.Dot(delta, transformedNormal) - joint.radius; + + if (distance < 0) + { + // ジョイントとコライダーの距離の方向。衝突している場合、この方向にジョイントを押し出す + var direction = transformedNormal; + nextTail -= direction * distance; + } + } } } \ No newline at end of file diff --git a/Assets/VRM10/Runtime/Format/Animation/Deserializer.g.cs b/Assets/VRM10/Runtime/Format/Animation/Deserializer.g.cs index d74221b5b..7b8660150 100644 --- a/Assets/VRM10/Runtime/Format/Animation/Deserializer.g.cs +++ b/Assets/VRM10/Runtime/Format/Animation/Deserializer.g.cs @@ -1921,26 +1921,6 @@ public static Preset __expressions_Deserialize_Preset(JsonNode parsed) continue; } - if(key=="lookUp"){ - value.LookUp = __expressions__preset_Deserialize_LookUp(kv.Value); - continue; - } - - if(key=="lookDown"){ - value.LookDown = __expressions__preset_Deserialize_LookDown(kv.Value); - continue; - } - - if(key=="lookLeft"){ - value.LookLeft = __expressions__preset_Deserialize_LookLeft(kv.Value); - continue; - } - - if(key=="lookRight"){ - value.LookRight = __expressions__preset_Deserialize_LookRight(kv.Value); - continue; - } - if(key=="neutral"){ value.Neutral = __expressions__preset_Deserialize_Neutral(kv.Value); continue; @@ -2301,114 +2281,6 @@ public static Expression __expressions__preset_Deserialize_BlinkRight(JsonNode p return value; } -public static Expression __expressions__preset_Deserialize_LookUp(JsonNode parsed) -{ - var value = new Expression(); - - 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; - } - - } - return value; -} - -public static Expression __expressions__preset_Deserialize_LookDown(JsonNode parsed) -{ - var value = new Expression(); - - 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; - } - - } - return value; -} - -public static Expression __expressions__preset_Deserialize_LookLeft(JsonNode parsed) -{ - var value = new Expression(); - - 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; - } - - } - return value; -} - -public static Expression __expressions__preset_Deserialize_LookRight(JsonNode parsed) -{ - var value = new Expression(); - - 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; - } - - } - return value; -} - public static Expression __expressions__preset_Deserialize_Neutral(JsonNode parsed) { var value = new Expression(); @@ -2496,9 +2368,25 @@ public static LookAt Deserialize_LookAt(JsonNode parsed) continue; } + if(key=="offsetFromHeadBone"){ + value.OffsetFromHeadBone = __lookAt_Deserialize_OffsetFromHeadBone(kv.Value); + continue; + } + } return value; } +public static float[] __lookAt_Deserialize_OffsetFromHeadBone(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/Assets/VRM10/Runtime/Format/Animation/Format.g.cs b/Assets/VRM10/Runtime/Format/Animation/Format.g.cs index df697d818..f7a1d5f03 100644 --- a/Assets/VRM10/Runtime/Format/Animation/Format.g.cs +++ b/Assets/VRM10/Runtime/Format/Animation/Format.g.cs @@ -251,18 +251,6 @@ namespace UniGLTF.Extensions.VRMC_vrm_animation // Represents a single expression. public Expression BlinkRight; - // Represents a single expression. - public Expression LookUp; - - // Represents a single expression. - public Expression LookDown; - - // Represents a single expression. - public Expression LookLeft; - - // Represents a single expression. - public Expression LookRight; - // Represents a single expression. public Expression Neutral; } @@ -292,6 +280,9 @@ namespace UniGLTF.Extensions.VRMC_vrm_animation // Represents a single glTF node represents the eye gaze point. public int? Node; + + // The position offset of the origin of the LookAt from the humanoid head bone + public float[] OffsetFromHeadBone; } public class VRMC_vrm_animation diff --git a/Assets/VRM10/Runtime/Format/Animation/Serializer.g.cs b/Assets/VRM10/Runtime/Format/Animation/Serializer.g.cs index d27bd05a6..996c1ccb5 100644 --- a/Assets/VRM10/Runtime/Format/Animation/Serializer.g.cs +++ b/Assets/VRM10/Runtime/Format/Animation/Serializer.g.cs @@ -1689,26 +1689,6 @@ public static void __expressions_Serialize_Preset(JsonFormatter f, Preset value) __expressions__preset_Serialize_BlinkRight(f, value.BlinkRight); } - if(value.LookUp!=null){ - f.Key("lookUp"); - __expressions__preset_Serialize_LookUp(f, value.LookUp); - } - - if(value.LookDown!=null){ - f.Key("lookDown"); - __expressions__preset_Serialize_LookDown(f, value.LookDown); - } - - if(value.LookLeft!=null){ - f.Key("lookLeft"); - __expressions__preset_Serialize_LookLeft(f, value.LookLeft); - } - - if(value.LookRight!=null){ - f.Key("lookRight"); - __expressions__preset_Serialize_LookRight(f, value.LookRight); - } - if(value.Neutral!=null){ f.Key("neutral"); __expressions__preset_Serialize_Neutral(f, value.Neutral); @@ -2016,98 +1996,6 @@ public static void __expressions__preset_Serialize_BlinkRight(JsonFormatter f, E f.EndMap(); } -public static void __expressions__preset_Serialize_LookUp(JsonFormatter f, Expression 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()); - } - - f.EndMap(); -} - -public static void __expressions__preset_Serialize_LookDown(JsonFormatter f, Expression 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()); - } - - f.EndMap(); -} - -public static void __expressions__preset_Serialize_LookLeft(JsonFormatter f, Expression 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()); - } - - f.EndMap(); -} - -public static void __expressions__preset_Serialize_LookRight(JsonFormatter f, Expression 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()); - } - - f.EndMap(); -} - public static void __expressions__preset_Serialize_Neutral(JsonFormatter f, Expression value) { f.BeginMap(); @@ -2187,8 +2075,25 @@ public static void Serialize_LookAt(JsonFormatter f, LookAt value) f.Value(value.Node.GetValueOrDefault()); } + if(value.OffsetFromHeadBone!=null&&value.OffsetFromHeadBone.Count()>=3){ + f.Key("offsetFromHeadBone"); + __lookAt_Serialize_OffsetFromHeadBone(f, value.OffsetFromHeadBone); + } + f.EndMap(); } +public static void __lookAt_Serialize_OffsetFromHeadBone(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + } // class } // namespace diff --git a/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider.meta b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider.meta new file mode 100644 index 000000000..6ea3f5ae0 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4feacc5eab0e46e4b900d239ff3c6485 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Deserializer.g.cs b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Deserializer.g.cs new file mode 100644 index 000000000..2630d2a80 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Deserializer.g.cs @@ -0,0 +1,238 @@ +// 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_extended_collider { + +public static class GltfDeserializer +{ + public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(VRMC_springBone_extended_collider.ExtensionName); + +public static bool TryGet(UniGLTF.glTFExtension src, out VRMC_springBone_extended_collider 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_extended_collider Deserialize(JsonNode parsed) +{ + var value = new VRMC_springBone_extended_collider(); + + 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=="shape"){ + value.Shape = Deserialize_Shape(kv.Value); + continue; + } + + } + return value; +} + +public static ExtendedColliderShape Deserialize_Shape(JsonNode parsed) +{ + var value = new ExtendedColliderShape(); + + 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=="sphere"){ + value.Sphere = __shape_Deserialize_Sphere(kv.Value); + continue; + } + + if(key=="capsule"){ + value.Capsule = __shape_Deserialize_Capsule(kv.Value); + continue; + } + + if(key=="plane"){ + value.Plane = __shape_Deserialize_Plane(kv.Value); + continue; + } + + } + return value; +} + +public static ExtendedColliderShapeSphere __shape_Deserialize_Sphere(JsonNode parsed) +{ + var value = new ExtendedColliderShapeSphere(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="offset"){ + value.Offset = __shape__sphere_Deserialize_Offset(kv.Value); + continue; + } + + if(key=="radius"){ + value.Radius = kv.Value.GetSingle(); + continue; + } + + if(key=="inside"){ + value.Inside = kv.Value.GetBoolean(); + continue; + } + + } + return value; +} + +public static float[] __shape__sphere_Deserialize_Offset(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 ExtendedColliderShapeCapsule __shape_Deserialize_Capsule(JsonNode parsed) +{ + var value = new ExtendedColliderShapeCapsule(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="offset"){ + value.Offset = __shape__capsule_Deserialize_Offset(kv.Value); + continue; + } + + if(key=="radius"){ + value.Radius = kv.Value.GetSingle(); + continue; + } + + if(key=="tail"){ + value.Tail = __shape__capsule_Deserialize_Tail(kv.Value); + continue; + } + + if(key=="inside"){ + value.Inside = kv.Value.GetBoolean(); + continue; + } + + } + return value; +} + +public static float[] __shape__capsule_Deserialize_Offset(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[] __shape__capsule_Deserialize_Tail(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 ExtendedColliderShapePlane __shape_Deserialize_Plane(JsonNode parsed) +{ + var value = new ExtendedColliderShapePlane(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="offset"){ + value.Offset = __shape__plane_Deserialize_Offset(kv.Value); + continue; + } + + if(key=="normal"){ + value.Normal = __shape__plane_Deserialize_Normal(kv.Value); + continue; + } + + } + return value; +} + +public static float[] __shape__plane_Deserialize_Offset(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[] __shape__plane_Deserialize_Normal(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/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Deserializer.g.cs.meta b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Deserializer.g.cs.meta new file mode 100644 index 000000000..fdf943ac1 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Deserializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 03ad1a44a20bbc94db18a7ee01cd6d3e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Format.g.cs b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Format.g.cs new file mode 100644 index 000000000..8ce05a98f --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Format.g.cs @@ -0,0 +1,76 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; + + +namespace UniGLTF.Extensions.VRMC_springBone_extended_collider +{ + + public class ExtendedColliderShapeSphere + { + // The offset of the sphere from the origin in the local space. + public float[] Offset; + + // The radius of the sphere. + public float? Radius; + + // If true, the collider prevents spring bones from going outside of the sphere instead. + public bool? Inside; + } + + public class ExtendedColliderShapeCapsule + { + // The offset of the capsule head from the origin in the local space. + public float[] Offset; + + // The radius of the capsule. + public float? Radius; + + // The offset of the capsule tail from the origin in the local space. + public float[] Tail; + + // If true, the collider prevents spring bones from going outside of the capsule instead. + public bool? Inside; + } + + public class ExtendedColliderShapePlane + { + // The offset of the plane from the origin in the local space. + public float[] Offset; + + // The normal of the plane in the local space. Must be normalized. + public float[] Normal; + } + + public class ExtendedColliderShape + { + // Dictionary object with extension-specific objects. + public object Extensions; + + // Application-specific data. + public object Extras; + + public ExtendedColliderShapeSphere Sphere; + + public ExtendedColliderShapeCapsule Capsule; + + public ExtendedColliderShapePlane Plane; + } + + public class VRMC_springBone_extended_collider + { + public const string ExtensionName = "VRMC_springBone_extended_collider"; + + // Dictionary object with extension-specific objects. + public object Extensions; + + // Application-specific data. + public object Extras; + + // Specification version of VRMC_springBone_extended_collider. + public string SpecVersion; + + // The shape of the collider. + public ExtendedColliderShape Shape; + } +} diff --git a/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Format.g.cs.meta b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Format.g.cs.meta new file mode 100644 index 000000000..960a93d31 --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Format.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afa332976c5ea28468f9a63b0b3ad344 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Serializer.g.cs b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Serializer.g.cs new file mode 100644 index 000000000..bfc86ffdd --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Serializer.g.cs @@ -0,0 +1,222 @@ +// 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_extended_collider { + + static public class GltfSerializer + { + + public static void SerializeTo(ref UniGLTF.glTFExtension dst, VRMC_springBone_extended_collider 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_extended_collider.ExtensionName, f.GetStoreBytes()); + } + + +public static void Serialize(JsonFormatter f, VRMC_springBone_extended_collider 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.Shape!=null){ + f.Key("shape"); + Serialize_Shape(f, value.Shape); + } + + f.EndMap(); +} + +public static void Serialize_Shape(JsonFormatter f, ExtendedColliderShape 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.Sphere!=null){ + f.Key("sphere"); + __shape_Serialize_Sphere(f, value.Sphere); + } + + if(value.Capsule!=null){ + f.Key("capsule"); + __shape_Serialize_Capsule(f, value.Capsule); + } + + if(value.Plane!=null){ + f.Key("plane"); + __shape_Serialize_Plane(f, value.Plane); + } + + f.EndMap(); +} + +public static void __shape_Serialize_Sphere(JsonFormatter f, ExtendedColliderShapeSphere value) +{ + f.BeginMap(); + + + if(value.Offset!=null&&value.Offset.Count()>=3){ + f.Key("offset"); + __shape__sphere_Serialize_Offset(f, value.Offset); + } + + if(value.Radius.HasValue){ + f.Key("radius"); + f.Value(value.Radius.GetValueOrDefault()); + } + + if(value.Inside.HasValue){ + f.Key("inside"); + f.Value(value.Inside.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void __shape__sphere_Serialize_Offset(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void __shape_Serialize_Capsule(JsonFormatter f, ExtendedColliderShapeCapsule value) +{ + f.BeginMap(); + + + if(value.Offset!=null&&value.Offset.Count()>=3){ + f.Key("offset"); + __shape__capsule_Serialize_Offset(f, value.Offset); + } + + if(value.Radius.HasValue){ + f.Key("radius"); + f.Value(value.Radius.GetValueOrDefault()); + } + + if(value.Tail!=null&&value.Tail.Count()>=3){ + f.Key("tail"); + __shape__capsule_Serialize_Tail(f, value.Tail); + } + + if(value.Inside.HasValue){ + f.Key("inside"); + f.Value(value.Inside.GetValueOrDefault()); + } + + f.EndMap(); +} + +public static void __shape__capsule_Serialize_Offset(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void __shape__capsule_Serialize_Tail(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void __shape_Serialize_Plane(JsonFormatter f, ExtendedColliderShapePlane value) +{ + f.BeginMap(); + + + if(value.Offset!=null&&value.Offset.Count()>=3){ + f.Key("offset"); + __shape__plane_Serialize_Offset(f, value.Offset); + } + + if(value.Normal!=null&&value.Normal.Count()>=3){ + f.Key("normal"); + __shape__plane_Serialize_Normal(f, value.Normal); + } + + f.EndMap(); +} + +public static void __shape__plane_Serialize_Offset(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void __shape__plane_Serialize_Normal(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + + } // class +} // namespace diff --git a/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Serializer.g.cs.meta b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Serializer.g.cs.meta new file mode 100644 index 000000000..43d7c548c --- /dev/null +++ b/Assets/VRM10/Runtime/Format/SpringBoneExtendedCollider/Serializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a96731f2fb44d4d43a94e9c52d4361b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs b/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs index 62144854f..7a26cd8d5 100644 --- a/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Assets/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -324,6 +324,68 @@ namespace UniVRM10 return shape; } + static UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShape ExportShapeExtended(VRM10SpringBoneCollider z) + { + var shape = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShape(); + switch (z.ColliderType) + { + case VRM10SpringBoneColliderTypes.Sphere: + { + shape.Sphere = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeSphere + { + Radius = z.Radius, + Offset = ReverseX(z.Offset), + }; + break; + } + + case VRM10SpringBoneColliderTypes.Capsule: + { + shape.Capsule = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeCapsule + { + Radius = z.Radius, + Offset = ReverseX(z.Offset), + Tail = ReverseX(z.Tail), + }; + break; + } + + case VRM10SpringBoneColliderTypes.SphereInside: + { + shape.Sphere = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeSphere + { + Radius = z.Radius, + Offset = ReverseX(z.Offset), + Inside = true, + }; + break; + } + + case VRM10SpringBoneColliderTypes.CapsuleInside: + { + shape.Capsule = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeCapsule + { + Radius = z.Radius, + Offset = ReverseX(z.Offset), + Tail = ReverseX(z.Tail), + }; + break; + } + + case VRM10SpringBoneColliderTypes.Plane: + { + shape.Plane = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapePlane + { + Offset = ReverseX(z.Offset), + Normal = ReverseX(z.Normal), + }; + break; + } + break; + } + return shape; + } + static UniGLTF.Extensions.VRMC_springBone.SpringBoneJoint ExportJoint(VRM10SpringBoneJoint y, Func getIndexFromTransform) { var joint = new UniGLTF.Extensions.VRMC_springBone.SpringBoneJoint @@ -369,11 +431,22 @@ namespace UniVRM10 foreach (var c in colliders) { - springBone.Colliders.Add(new UniGLTF.Extensions.VRMC_springBone.Collider + var exportCollider = new UniGLTF.Extensions.VRMC_springBone.Collider { Node = getNodeIndexFromTransform(c.transform), Shape = ExportShape(c), - }); + }; + if (c.ColliderType == VRM10SpringBoneColliderTypes.SphereInside + || c.ColliderType == VRM10SpringBoneColliderTypes.CapsuleInside + || c.ColliderType == VRM10SpringBoneColliderTypes.Plane + ) + { + exportCollider.Extensions = new UniGLTF.Extensions.VRMC_springBone_extended_collider.VRMC_springBone_extended_collider + { + Shape = ExportShapeExtended(c), + }; + } + springBone.Colliders.Add(exportCollider); } // colliderGroups diff --git a/Assets/VRM10/Runtime/IO/Vrm10Importer.cs b/Assets/VRM10/Runtime/IO/Vrm10Importer.cs index 65b201ae4..98d7ac94b 100644 --- a/Assets/VRM10/Runtime/IO/Vrm10Importer.cs +++ b/Assets/VRM10/Runtime/IO/Vrm10Importer.cs @@ -538,6 +538,41 @@ namespace UniVRM10 { throw new Vrm10Exception("unknown shape"); } + + // https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_springBone_extended_collider-1.0 + // VRMC_springBone_extended_collider + if (UniGLTF.Extensions.VRMC_springBone_extended_collider.GltfDeserializer.TryGet(c.Extensions as glTFExtension, + out UniGLTF.Extensions.VRMC_springBone_extended_collider.VRMC_springBone_extended_collider exCollider)) + { + if (exCollider.Shape.Sphere is UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeSphere exSphere) + { + collider.ColliderType = exSphere.Inside.GetValueOrDefault() ? VRM10SpringBoneColliderTypes.SphereInside : VRM10SpringBoneColliderTypes.Sphere; + collider.Offset = Vector3InvertX(exSphere.Offset); + collider.Radius = exSphere.Radius.Value; + if (exSphere.Inside.HasValue && exSphere.Inside.Value) + { + collider.ColliderType = VRM10SpringBoneColliderTypes.SphereInside; + } + } + else if (exCollider.Shape.Capsule is UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeCapsule exCapsule) + { + collider.ColliderType = exCapsule.Inside.GetValueOrDefault() ? VRM10SpringBoneColliderTypes.CapsuleInside : VRM10SpringBoneColliderTypes.Capsule; + collider.Offset = Vector3InvertX(exCapsule.Offset); + collider.Tail = Vector3InvertX(exCapsule.Tail); + collider.Radius = exCapsule.Radius.Value; + } + else if (exCollider.Shape.Plane is UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapePlane exPlane) + { + collider.ColliderType = VRM10SpringBoneColliderTypes.Plane; + collider.Offset = Vector3InvertX(exPlane.Offset); + collider.Normal = Vector3InvertX(exPlane.Normal); + collider.Radius = 0.1f; // gizmo visualize. 10cm + } + else + { + throw new Vrm10Exception("VRMC_springBone_extended_collider: unknown shape"); + } + } } } diff --git a/Assets/VRM10/Runtime/IO/VrmAnimationImporter.cs b/Assets/VRM10/Runtime/IO/VrmAnimationImporter.cs index e52a898dc..7150476bf 100644 --- a/Assets/VRM10/Runtime/IO/VrmAnimationImporter.cs +++ b/Assets/VRM10/Runtime/IO/VrmAnimationImporter.cs @@ -181,10 +181,6 @@ namespace UniVRM10 { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.blink), nameof(Vrm10AnimationInstance.preset_blink), preset.Blink) is ExpressionInfo info) yield return info; } { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.blinkLeft), nameof(Vrm10AnimationInstance.preset_blinkleft), preset.BlinkLeft) is ExpressionInfo info) yield return info; } { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.blinkRight), nameof(Vrm10AnimationInstance.preset_blinkright), preset.BlinkRight) is ExpressionInfo info) yield return info; } - { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.lookUp), "preset_lookup", preset.LookUp) is ExpressionInfo info) yield return info; } - { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.lookDown), "preset_lookdown", preset.LookDown) is ExpressionInfo info) yield return info; } - { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.lookLeft), "preset_lookleft", preset.LookLeft) is ExpressionInfo info) yield return info; } - { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.lookRight), "preset_lookright", preset.LookRight) is ExpressionInfo info) yield return info; } { if (GetExpression(gltfAnimation, ExpressionKey.CreateFromPreset(ExpressionPreset.neutral), nameof(Vrm10AnimationInstance.preset_neutral), preset.Neutral) is ExpressionInfo info) yield return info; } } if (animation.Expressions.Custom != null) diff --git a/vrm-specification b/vrm-specification index 0bcc3d4c5..7f5252a94 160000 --- a/vrm-specification +++ b/vrm-specification @@ -1 +1 @@ -Subproject commit 0bcc3d4c56311ea36905049b10908a4eb60aa327 +Subproject commit 7f5252a94208fc127b59eb4ce2fd699e6ae3ca6e