From 737a4888e348d516bfd1cddaa58f5685483a2b99 Mon Sep 17 00:00:00 2001 From: yutopp Date: Sun, 5 Jun 2022 22:47:44 +0900 Subject: [PATCH 01/11] Fix glTF type definitions for animations --- Packages/net.yutopp.vgltf/Runtime/Types/Animation.cs | 10 +++++----- Packages/net.yutopp.vgltf/Runtime/Types/Gltf.cs | 12 ++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Packages/net.yutopp.vgltf/Runtime/Types/Animation.cs b/Packages/net.yutopp.vgltf/Runtime/Types/Animation.cs index d0a23b8..146cd67 100644 --- a/Packages/net.yutopp.vgltf/Runtime/Types/Animation.cs +++ b/Packages/net.yutopp.vgltf/Runtime/Types/Animation.cs @@ -26,7 +26,7 @@ public sealed class Animation : GltfChildOfRootProperty // [JsonSchema(Id = "animation.channel.schema.json")] - public class ChannelType : GltfProperty + public sealed class ChannelType : GltfProperty { [JsonField(Name = "sampler")] [JsonSchemaRequired, JsonSchemaRef(typeof(GltfID))] @@ -39,11 +39,11 @@ public class ChannelType : GltfProperty // [JsonSchema(Id = "animation.channel.target.schema.json")] - public class TargetType : GltfProperty + public sealed class TargetType : GltfProperty { - [JsonField(Name = "node")] + [JsonField(Name = "node"), JsonFieldIgnorable] [JsonSchemaRef(typeof(GltfID))] - public int Node; + public int? Node; [JsonField(Name = "path")] [JsonSchemaRequired] @@ -67,7 +67,7 @@ public enum PathEnum } [JsonSchema(Id = "animation.sampler.schema.json")] - public class SamplerType + public sealed class SamplerType : GltfProperty { [JsonField(Name = "input")] [JsonSchemaRequired, JsonSchemaRef(typeof(GltfID))] diff --git a/Packages/net.yutopp.vgltf/Runtime/Types/Gltf.cs b/Packages/net.yutopp.vgltf/Runtime/Types/Gltf.cs index 594bace..1308899 100644 --- a/Packages/net.yutopp.vgltf/Runtime/Types/Gltf.cs +++ b/Packages/net.yutopp.vgltf/Runtime/Types/Gltf.cs @@ -271,6 +271,18 @@ public static int AddSkin(this Gltf gltf, Skin item) return n; } + public static int AddAnimation(this Gltf gltf, Animation item) + { + if (gltf.Animations == null) + { + gltf.Animations = new List(); + } + + var n = gltf.Animations.Count; + gltf.Animations.Add(item); + return n; + } + public static void AddExtensionUsed(this Gltf gltf, string name) { if (gltf.ExtensionsUsed == null) From 072e708eb0714be49cb3d9a8449abdac98c72906 Mon Sep 17 00:00:00 2001 From: yutopp Date: Sun, 5 Jun 2022 22:49:51 +0900 Subject: [PATCH 02/11] Add exporters and hooks for Animations --- .../Runtime/AnimationExporter.cs | 49 +++++++++++++++++++ .../Runtime/AnimationExporter.cs.meta | 11 +++++ .../Runtime/Exporter.cs | 32 ++++++++++-- .../Runtime/ExporterRuntimeResources.cs | 1 + .../Runtime/IExporterContext.cs | 1 + .../Runtime/PrimitiveExporter.cs | 15 ++++++ 6 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs.meta diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs new file mode 100644 index 0000000..20349a8 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs @@ -0,0 +1,49 @@ +// +// Copyright (c) 2022- yutopp (yutopp@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using UnityEngine; + +namespace VGltf.Unity +{ + public abstract class AnimationExporterHook + { + public abstract IndexedResource Export(IExporterContext context, AnimationClip clip); + } + + public sealed class AnimationExporter : ExporterRefHookable + { + public override IExporterContext Context { get; } + + public AnimationExporter(IExporterContext context) + { + Context = context; + } + + public IndexedResource Export(AnimationClip clip) + { + return Context.Resources.Animations.GetOrCall(clip, () => + { + return ForceExport(clip); + }); + } + + public IndexedResource ForceExport(AnimationClip clip) + { + foreach (var h in Hooks) + { + var r = h.Export(Context, clip); + if (r != null) + { + return r; + } + } + + throw new NotImplementedException(); + } + } +} diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs.meta b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs.meta new file mode 100644 index 0000000..f9f5d83 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 758df19b3e6374b5eacfc0e223240ff3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Exporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Exporter.cs index f9f85e6..14ec5b1 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/Exporter.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Exporter.cs @@ -69,6 +69,7 @@ public InnerContext(Config config) Materials = new MaterialExporter(this, materialExporterConfig), Textures = new TextureExporter(this), Images = new ImageExporter(this), + Animations = new AnimationExporter(this), }; } @@ -102,23 +103,33 @@ public Exporter(Config config = null) }; } - public void ExportGameObjectAsScene(GameObject go) + public void ExportGameObjectAsScene(GameObject go, AnimationClip[] clips = null) { if (_config.UseNormalizedTransforms) { using (var normalizer = new VGltf.Unity.Ext.TransformNormalizer()) { normalizer.Normalize(go); - ExportGameObjectAsSceneWithoutNormalize(normalizer.Go); + ExportGameObjectAsSceneWithoutNormalize(normalizer.Go, clips); } } else { - ExportGameObjectAsSceneWithoutNormalize(go); + ExportGameObjectAsSceneWithoutNormalize(go, clips); } } - void ExportGameObjectAsSceneWithoutNormalize(GameObject go) + public void ExportOnlyAnimationClips(AnimationClip[] clips) + { + ExportAnimationClipsInternal(clips); + + foreach (var hook in Hooks) + { + hook.PostHook(this, null); + } + } + + void ExportGameObjectAsSceneWithoutNormalize(GameObject go, AnimationClip[] clips) { Func[]> generator = () => { @@ -145,12 +156,25 @@ void ExportGameObjectAsSceneWithoutNormalize(GameObject go) }); Context.Gltf.Scene = rootSceneIndex; + if (clips != null) + { + ExportAnimationClipsInternal(clips); + } + foreach (var hook in Hooks) { hook.PostHook(this, go); } } + void ExportAnimationClipsInternal(AnimationClip[] clips) + { + foreach(var clip in clips) + { + Context.Exporters.Animations.Export(clip); + } + } + public GltfContainer IntoGlbContainer() { // Buffer diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/ExporterRuntimeResources.cs b/Packages/net.yutopp.vgltf.unity/Runtime/ExporterRuntimeResources.cs index ec330cb..cf279d5 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/ExporterRuntimeResources.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/ExporterRuntimeResources.cs @@ -18,6 +18,7 @@ public sealed class ExporterRuntimeResources : IDisposable public IndexedResourceDict Materials = new IndexedResourceDict(); public IndexedResourceDict Meshes = new IndexedResourceDict(); public IndexedResourceDict Skins = new IndexedResourceDict(); + public IndexedResourceDict Animations = new IndexedResourceDict(); public void Dispose() { diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/IExporterContext.cs b/Packages/net.yutopp.vgltf.unity/Runtime/IExporterContext.cs index 38fbad4..0bb5fab 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/IExporterContext.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/IExporterContext.cs @@ -27,5 +27,6 @@ public sealed class ResourceExporters public MaterialExporter Materials; public TextureExporter Textures; public ImageExporter Images; + public AnimationExporter Animations; } } diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/PrimitiveExporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/PrimitiveExporter.cs index 7f04d8b..edd8278 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/PrimitiveExporter.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/PrimitiveExporter.cs @@ -64,6 +64,21 @@ public static byte[] Marshal(int[] arr) return buffer; } + public static byte[] Marshal(float[] arr) + { + // TODO: optimize + using (var ms = new MemoryStream()) + using (var w = new BinaryWriter(ms)) + { + foreach (var v in arr) + { + w.Write(v); + } + w.Flush(); + return ms.ToArray(); + } + } + public static byte[] Marshal(Vector2[] vec2) { // TODO: optimize From a37de49fcf20ec7de1a953b159b1e1affc21d362 Mon Sep 17 00:00:00 2001 From: yutopp Date: Sun, 5 Jun 2022 22:50:46 +0900 Subject: [PATCH 03/11] Add importers and hooks for Animations --- .../Runtime/AnimationImporter.cs | 51 +++++++++++++++++++ .../Runtime/AnimationImporter.cs.meta | 11 ++++ .../Runtime/IImporterContext.cs | 1 + .../Runtime/Importer.cs | 20 ++++++++ .../Runtime/ImporterRuntimeResources.cs | 2 + 5 files changed, 85 insertions(+) create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs.meta diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs new file mode 100644 index 0000000..b109efc --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) 2022- yutopp (yutopp@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; + +namespace VGltf.Unity +{ + public abstract class AnimationImporterHook + { + public abstract Task> Import(IImporterContext context, int animIndex, CancellationToken ct); + } + + public sealed class AnimationImporter : ImporterRefHookable + { + public override IImporterContext Context { get; } + + public AnimationImporter(IImporterContext context) + { + Context = context; + } + + public async Task> Import(int animIndex, CancellationToken ct) + { + return await Context.Resources.Animations.GetOrCallAsync(animIndex, async () => + { + return await ForceImport(animIndex, ct); + }); + } + + public async Task> ForceImport(int animIndex, CancellationToken ct) + { + foreach (var h in Hooks) + { + var r = await h.Import(Context, animIndex, ct); + if (r != null) + { + return r; + } + } + + throw new NotImplementedException(); + } + } +} diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs.meta b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs.meta new file mode 100644 index 0000000..4c340a7 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 42783c9d09084417094152faed19242c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/IImporterContext.cs b/Packages/net.yutopp.vgltf.unity/Runtime/IImporterContext.cs index be80b57..a1e0583 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/IImporterContext.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/IImporterContext.cs @@ -29,6 +29,7 @@ public sealed class ResourceImporters public MaterialImporter Materials; public TextureImporter Textures; public ImageImporter Images; + public AnimationImporter Animations; } public sealed class ImportingSetting diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Importer.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Importer.cs index dbda532..c057010 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/Importer.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Importer.cs @@ -95,6 +95,7 @@ public InnerContext(GltfContainer container, IResourceLoader loader, ITimeSlicer Materials = new MaterialImporter(this, materialImporterConfig), Textures = new TextureImporter(this), Images = new ImageImporter(this), + Animations = new AnimationImporter(this), }; } @@ -170,6 +171,25 @@ public async Task ImportSceneNodes(CancellationToken ct) return TakeContext(); } + public async Task ImportOnlyAnimations(CancellationToken ct) + { + var gltf = Context.Container.Gltf; + + for(var i=0; i Textures = new IndexedResourceDict(); public IndexedResourceDict Materials = new IndexedResourceDict(); public IndexedResourceDict Meshes = new IndexedResourceDict(); + public IndexedResourceDict Animations = new IndexedResourceDict(); public Dictionary AuxResources { get; } = new Dictionary(); public void Dispose() { + Animations.Dispose(); Nodes.Dispose(); Meshes.Dispose(); Materials.Dispose(); From a6a91687e1a5e2e6b22455c5f74da14799ef0cf4 Mon Sep 17 00:00:00 2001 From: yutopp Date: Sun, 5 Jun 2022 22:51:40 +0900 Subject: [PATCH 04/11] Add VGltf extras for importing/exporting Unity humanoid animations --- .../Runtime/Extra/Helper.meta | 8 + .../Extra/Helper/HumanoidAnimationExporter.cs | 148 ++++++++++++++++++ .../Helper/HumanoidAnimationExporter.cs.meta | 11 ++ .../Extra/Helper/HumanoidAnimationImporter.cs | 98 ++++++++++++ .../Helper/HumanoidAnimationImporter.cs.meta | 11 ++ .../Runtime/Extra/Helper/Types.meta | 8 + .../Helper/Types/HumanoidAnimationType.cs | 28 ++++ .../Types/HumanoidAnimationType.cs.meta | 11 ++ 8 files changed, 323 insertions(+) create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper.meta create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs.meta create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs.meta create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types.meta create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs.meta diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper.meta b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper.meta new file mode 100644 index 0000000..bc51072 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ee35cfd37da454db6bd687370998633f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs new file mode 100644 index 0000000..6f728b0 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs @@ -0,0 +1,148 @@ +// +// Copyright (c) 2022- yutopp (yutopp@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Collections.Generic; +using System.Linq; +#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine; +using VGltf.Types.Extensions; + +namespace VGltf.Unity.Ext.Helper +{ + public sealed class HumanoidAnimationExporter : AnimationExporterHook + { +#if UNITY_EDITOR + public override IndexedResource Export(IExporterContext context, AnimationClip clip) + { + // If clip is not HumanMotion, Do nothing. + if (!clip.isHumanMotion) + { + return null; + } + + var curveBindings = AnimationUtility.GetCurveBindings(clip); + + var channels = new List(); + var samplers = new List(); + + foreach (var binding in curveBindings) + { + Debug.Log($"{binding.path} :: {binding.propertyName})"); + var curve = AnimationUtility.GetEditorCurve(clip, binding); + + // TODO: Support CUBICSPLINE + + var timestamps = new float[curve.keys.Length]; + var values = new float[curve.keys.Length]; + + foreach (var (keyframe, index) in curve.keys.Select((v, i) => (v, i))) + { + timestamps[index] = keyframe.time; + values[index] = keyframe.value; + } + + var inputAccessorId = ExportTimestamp(context, timestamps); + var outputAccessorId = ExportHumanoidValue(context, values); + + var samplerId = samplers.Count; + + var sampler = new VGltf.Types.Animation.SamplerType + { + Input = inputAccessorId, + // Interpolation + Output = outputAccessorId, + }; + samplers.Add(sampler); + + var channel = new VGltf.Types.Animation.ChannelType + { + Sampler = samplerId, + // NOTE: Target will not be used when "VGLTF_unity_humanoid_animation_channel" specified + Target = new VGltf.Types.Animation.ChannelType.TargetType + { + Path = VGltf.Types.Animation.ChannelType.TargetType.PathEnum.Translation, + }, + }; + channel.AddExtra(Types.HumanoidAnimationType.ChannelType.ExtraName, new Types.HumanoidAnimationType.ChannelType + { + PropertyName = binding.propertyName + }); + channels.Add(channel); + } + + var gltfAnim = new VGltf.Types.Animation + { + Name = clip.name, + Channels = channels, + Samplers = samplers, + }; + gltfAnim.AddExtra(Types.HumanoidAnimationType.ExtraName, new Types.HumanoidAnimationType{}); + var animIndex = context.Gltf.AddAnimation(gltfAnim); + + var res = context.Resources.Animations.Add(clip, animIndex, clip.name, clip); + + return res; + } +#else + public override IndexedResource Export(IExporterContext context, AnimationClip clip) + { + throw new NotImplementedException("Could not export AnimationClips without Editor"); + } +#endif + + public static int ExportTimestamp(IExporterContext context, float[] timestamps) + { + // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#_animation_sampler_input + + // Scalar | FLOAT + + byte[] buffer = PrimitiveExporter.Marshal(timestamps); + var viewIndex = context.BufferBuilder.AddView( + new ArraySegment(buffer), + null, + VGltf.Types.BufferView.TargetEnum.ELEMENT_ARRAY_BUFFER); + + var viewComponentType = VGltf.Types.Accessor.ComponentTypeEnum.FLOAT; + + var accessor = new VGltf.Types.Accessor + { + BufferView = viewIndex, + ByteOffset = 0, + ComponentType = viewComponentType, + Count = timestamps.Length, + Type = VGltf.Types.Accessor.TypeEnum.Scalar, + }; + return context.Gltf.AddAccessor(accessor); + } + + public static int ExportHumanoidValue(IExporterContext context, float[] values) + { + // Scalar | FLOAT + + byte[] buffer = PrimitiveExporter.Marshal(values); + var viewIndex = context.BufferBuilder.AddView( + new ArraySegment(buffer), + null, + VGltf.Types.BufferView.TargetEnum.ELEMENT_ARRAY_BUFFER); + + var viewComponentType = VGltf.Types.Accessor.ComponentTypeEnum.FLOAT; + + var accessor = new VGltf.Types.Accessor + { + BufferView = viewIndex, + ByteOffset = 0, + ComponentType = viewComponentType, + Count = values.Length, + Type = VGltf.Types.Accessor.TypeEnum.Scalar, + }; + return context.Gltf.AddAccessor(accessor); + } + } +} diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs.meta b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs.meta new file mode 100644 index 0000000..c77c612 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60059d8a805bc4bc4abd68b5d3df0467 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs new file mode 100644 index 0000000..74a0fe4 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs @@ -0,0 +1,98 @@ +// +// Copyright (c) 2022- yutopp (yutopp@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; + +namespace VGltf.Unity.Ext.Helper +{ + public sealed class HumanoidAnimationImporter : AnimationImporterHook + { + public override Task> Import(IImporterContext context, int animIndex, CancellationToken ct) + { + var gltf = context.Container.Gltf; + var gltfAnim = gltf.Animations[animIndex]; + + var animExtra = default(Types.HumanoidAnimationType); + if (!gltfAnim.TryGetExtra(Types.HumanoidAnimationType.ExtraName, context.Container.JsonSchemas, out animExtra)) + { + // This importer aims to importing only HumanoidAnimation extras, thus skip process. + return null; + } + + var animClip = new AnimationClip(); + animClip.name = gltfAnim.Name; + + var resource = context.Resources.Animations.Add(animIndex, animIndex, animClip.name, animClip); + + foreach (var channel in gltfAnim.Channels) + { + var extra = default(Types.HumanoidAnimationType.ChannelType); + if (!channel.TryGetExtra(Types.HumanoidAnimationType.ChannelType.ExtraName, context.Container.JsonSchemas, out extra)) + { + throw new NotImplementedException(); + } + + var sampler = gltfAnim.Samplers[channel.Sampler]; + + var timestamps = ImportTimestamp(context, sampler.Input); + var values = ImportHumanoidValue(context, sampler.Output); + Debug.Assert(timestamps.Length == values.Length); + + // TODO: support interpolation + var keyframes = new Keyframe[timestamps.Length]; + for (var i = 0; i < timestamps.Length; ++i) + { + keyframes[i] = new Keyframe(timestamps[i], values[i]); + } + + var curve = new AnimationCurve(keyframes); + animClip.SetCurve("", typeof(Animator), extra.PropertyName, curve); + } + + return Task.FromResult(resource); + } + + public static float[] ImportTimestamp(IImporterContext context, int index) + { + // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#_animation_sampler_input + + // SCALAR | FLOAT + var buf = context.GltfResources.GetOrLoadTypedBufferByAccessorIndex(index); + var acc = buf.Accessor; + if (acc.Type == VGltf.Types.Accessor.TypeEnum.Scalar) + { + if (acc.ComponentType == VGltf.Types.Accessor.ComponentTypeEnum.FLOAT) + { + return buf.GetEntity((xs, i) => xs[i]).AsArray(); + } + } + + throw new NotImplementedException(); // TODO + } + + public static float[] ImportHumanoidValue(IImporterContext context, int index) + { + // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#_animation_sampler_input + + // SCALAR | FLOAT + var buf = context.GltfResources.GetOrLoadTypedBufferByAccessorIndex(index); + var acc = buf.Accessor; + if (acc.Type == VGltf.Types.Accessor.TypeEnum.Scalar) + { + if (acc.ComponentType == VGltf.Types.Accessor.ComponentTypeEnum.FLOAT) + { + return buf.GetEntity((xs, i) => xs[i]).AsArray(); + } + } + + throw new NotImplementedException(); // TODO + } + } +} diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs.meta b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs.meta new file mode 100644 index 0000000..f9ebf3e --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 05fbba6ec765849f688b36d3c6ace5e5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types.meta b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types.meta new file mode 100644 index 0000000..0b005a9 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fcca6173f054240ca9eb70c89744f0a7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs new file mode 100644 index 0000000..4f23ab9 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) 2022- yutopp (yutopp@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using VJson; + +namespace VGltf.Unity.Ext.Helper.Types +{ + /// + /// + [Json] + public sealed class HumanoidAnimationType + { + public static readonly string ExtraName = "VGLTF_unity_humanoid_animation"; + + [Json] + public sealed class ChannelType + { + public static readonly string ExtraName = "VGLTF_unity_humanoid_animation_channel"; + + [JsonField(Name = "propertyName")] + public string PropertyName; + } + } +} diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs.meta b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs.meta new file mode 100644 index 0000000..e6a4c86 --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a4fd875c047d41ad93fe6de702f031e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From fbf0ae301802468e0996258f01ccdb3a884d30c1 Mon Sep 17 00:00:00 2001 From: yutopp Date: Sun, 5 Jun 2022 22:52:29 +0900 Subject: [PATCH 05/11] Test: Use glTF animaions in VRM examples --- .../VRMExample/Scripts/VRMLoader.cs | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs b/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs index ad85dde..ef1340d 100644 --- a/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs +++ b/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs @@ -7,6 +7,7 @@ using UnityEngine.UI; using VGltf; using VGltf.Unity; +using System.Linq; namespace VGltfExamples.VRMExample { @@ -17,10 +18,13 @@ public sealed class VRMLoader : MonoBehaviour [SerializeField] Button unloadButton; [SerializeField] InputField outputFilePathInput; - [SerializeField] Button exportButton; + [SerializeField] Button exportButton; [SerializeField] public RuntimeAnimatorController RuntimeAnimatorController; + // デバッグ用 + [SerializeField] AnimationClip[] Clips; + sealed class VRMResource : IDisposable { public IImporterContext Context; @@ -118,6 +122,11 @@ void UIOnLoadButtonClick() async UniTaskVoid UIOnLoadButtonClickAsync() { +#if true + var context = await ImportClip("anim.glb"); // TODO: このままだとリソースリークするのでデバッグ終わったら消す + Clips = context.Resources.Animations.Map(clip => clip).Select(iv => iv.Value).ToArray(); +#endif + var p0 = Common.MemoryProfile.Now; DebugLogProfile(p0); @@ -166,6 +175,10 @@ async UniTaskVoid UIOnExportButtonClickedAsync() var head = _vrmResources[0]; var anim = head.Go.GetComponentInChildren(); + + var info = anim.GetCurrentAnimatorClipInfo(0); + var clip = info[0].clip; + var animCtrl = anim.runtimeAnimatorController; anim.runtimeAnimatorController = null; // Make the model to the rest pose @@ -203,6 +216,56 @@ await Task.Run(() => Debug.Log("exported"); }); + +#if true + await ExportClip(clip, "anim.glb"); +#endif + } + + async Task ExportClip(AnimationClip clip, string path) + { + GltfContainer gltfContainer = null; + using (var gltfExporter = new Exporter()) + { + gltfExporter.Context.Exporters.Animations.AddHook(new VGltf.Unity.Ext.Helper.HumanoidAnimationExporter()); + + gltfExporter.ExportOnlyAnimationClips(new AnimationClip[] { clip }); + gltfContainer = gltfExporter.IntoGlbContainer(); + } + + await Task.Run(() => + { + using (var fs = new FileStream(path, FileMode.OpenOrCreate)) + { + GltfContainer.ToGlb(fs, gltfContainer); + } + + Debug.Log("clip exported"); + }); + } + + async Task ImportClip(string path) + { + // Read the glTF container (unity-independent) + var gltfContainer = await Task.Run(() => + { + using (var fs = new FileStream(path, FileMode.Open)) + { + return GltfContainer.FromGlb(fs); + } + }); + + // Create a glTF Importer for Unity. + // The resources will be cached in the internal Context of this Importer. + // Resources can be released by calling Dispose of the Importer (or the internal Context). + var timeSlicer = new Common.TimeSlicer(); + using (var gltfImporter = new Importer(gltfContainer, timeSlicer)) + { + gltfImporter.Context.Importers.Animations.AddHook(new VGltf.Unity.Ext.Helper.HumanoidAnimationImporter()); + + // Load the Scene. + return await gltfImporter.ImportOnlyAnimations(System.Threading.CancellationToken.None); + } } void DebugLogProfile(Common.MemoryProfile now, Common.MemoryProfile prev = null) From 0b7325f520725e86e0bd5b5403fa56370dd3a60e Mon Sep 17 00:00:00 2001 From: yutopp Date: Mon, 6 Jun 2022 04:47:37 +0900 Subject: [PATCH 06/11] Relaxed interfaces to accept not only AnimaionClips. Support tangents/weights for HumanoidAnimations --- .../Runtime/AnimationImporter.cs | 6 +- .../Extra/Helper/HumanoidAnimationExporter.cs | 37 ++++++-- .../Extra/Helper/HumanoidAnimationImporter.cs | 46 ++++++++-- .../Helper/Types/HumanoidAnimationType.cs | 27 ++++++ .../Runtime/ImporterRuntimeResources.cs | 2 +- .../Runtime/IndexedResource.cs | 2 +- .../Runtime/IndexedResourceDict.cs | 89 ++++++++++++++++++- 7 files changed, 188 insertions(+), 21 deletions(-) diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs index b109efc..4983ca7 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs @@ -14,7 +14,7 @@ namespace VGltf.Unity { public abstract class AnimationImporterHook { - public abstract Task> Import(IImporterContext context, int animIndex, CancellationToken ct); + public abstract Task> Import(IImporterContext context, int animIndex, CancellationToken ct); } public sealed class AnimationImporter : ImporterRefHookable @@ -26,7 +26,7 @@ public AnimationImporter(IImporterContext context) Context = context; } - public async Task> Import(int animIndex, CancellationToken ct) + public async Task> Import(int animIndex, CancellationToken ct) { return await Context.Resources.Animations.GetOrCallAsync(animIndex, async () => { @@ -34,7 +34,7 @@ public async Task> Import(int animIndex, Cancella }); } - public async Task> ForceImport(int animIndex, CancellationToken ct) + public async Task> ForceImport(int animIndex, CancellationToken ct) { foreach (var h in Hooks) { diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs index 6f728b0..fedb532 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs @@ -34,33 +34,55 @@ public override IndexedResource Export(IExporterContext context, foreach (var binding in curveBindings) { - Debug.Log($"{binding.path} :: {binding.propertyName})"); var curve = AnimationUtility.GetEditorCurve(clip, binding); - // TODO: Support CUBICSPLINE - var timestamps = new float[curve.keys.Length]; var values = new float[curve.keys.Length]; + var inTangents = new float[curve.keys.Length]; + var inWeights = new float[curve.keys.Length]; + var outTangents = new float[curve.keys.Length]; + var outWeights = new float[curve.keys.Length]; + foreach (var (keyframe, index) in curve.keys.Select((v, i) => (v, i))) { timestamps[index] = keyframe.time; values[index] = keyframe.value; + + inTangents[index] = keyframe.inTangent; + inWeights[index] = keyframe.inWeight; + outTangents[index] = keyframe.outTangent; + outWeights[index] = keyframe.outWeight; + // keyframe.weightedMode } var inputAccessorId = ExportTimestamp(context, timestamps); - var outputAccessorId = ExportHumanoidValue(context, values); + var outputAccessorId = ExportHumanoidFloatScalarValue(context, values); + + var inTangentAccessorId = ExportHumanoidFloatScalarValue(context, inTangents); + var inWeightAccessorId = ExportHumanoidFloatScalarValue(context, inWeights); + var outTangentAccessorId = ExportHumanoidFloatScalarValue(context, outTangents); + var outWeightAccessorId = ExportHumanoidFloatScalarValue(context, outWeights); + // sampler var samplerId = samplers.Count; var sampler = new VGltf.Types.Animation.SamplerType { Input = inputAccessorId, - // Interpolation + // Interpolation will not be used when "VGLTF_unity_humanoid_animation_sampler" specified Output = outputAccessorId, }; + sampler.AddExtra(Types.HumanoidAnimationType.SamplerType.ExtraName, new Types.HumanoidAnimationType.SamplerType + { + InTangent = inTangentAccessorId, + InWeight = inWeightAccessorId, + OutTangent = outTangentAccessorId, + OutWeight = outWeightAccessorId, + }); samplers.Add(sampler); + // channel var channel = new VGltf.Types.Animation.ChannelType { Sampler = samplerId, @@ -72,11 +94,14 @@ public override IndexedResource Export(IExporterContext context, }; channel.AddExtra(Types.HumanoidAnimationType.ChannelType.ExtraName, new Types.HumanoidAnimationType.ChannelType { + RelativePath = binding.path, PropertyName = binding.propertyName }); channels.Add(channel); } + // TODO: Append standard animations if needed. + var gltfAnim = new VGltf.Types.Animation { Name = clip.name, @@ -122,7 +147,7 @@ public static int ExportTimestamp(IExporterContext context, float[] timestamps) return context.Gltf.AddAccessor(accessor); } - public static int ExportHumanoidValue(IExporterContext context, float[] values) + public static int ExportHumanoidFloatScalarValue(IExporterContext context, float[] values) { // Scalar | FLOAT diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs index 74a0fe4..3995ae6 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs @@ -6,6 +6,7 @@ // using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using UnityEngine; @@ -14,7 +15,14 @@ namespace VGltf.Unity.Ext.Helper { public sealed class HumanoidAnimationImporter : AnimationImporterHook { - public override Task> Import(IImporterContext context, int animIndex, CancellationToken ct) + readonly List _clips; + + public HumanoidAnimationImporter(List clips) + { + _clips = clips; + } + + public override Task> Import(IImporterContext context, int animIndex, CancellationToken ct) { var gltf = context.Container.Gltf; var gltfAnim = gltf.Animations[animIndex]; @@ -29,33 +37,53 @@ public override Task> Import(IImporterContext con var animClip = new AnimationClip(); animClip.name = gltfAnim.Name; - var resource = context.Resources.Animations.Add(animIndex, animIndex, animClip.name, animClip); + var resource = context.Resources.Animations.Add(animIndex, animIndex, animClip.name, new Utils.DestroyOnDispose(animClip)); foreach (var channel in gltfAnim.Channels) { - var extra = default(Types.HumanoidAnimationType.ChannelType); - if (!channel.TryGetExtra(Types.HumanoidAnimationType.ChannelType.ExtraName, context.Container.JsonSchemas, out extra)) + var channelExtra = default(Types.HumanoidAnimationType.ChannelType); + if (!channel.TryGetExtra(Types.HumanoidAnimationType.ChannelType.ExtraName, context.Container.JsonSchemas, out channelExtra)) { - throw new NotImplementedException(); + throw new NotImplementedException($"Extras {Types.HumanoidAnimationType.ChannelType.ExtraName} is not given"); } var sampler = gltfAnim.Samplers[channel.Sampler]; + var samplerExtra = default(Types.HumanoidAnimationType.SamplerType); + if (!sampler.TryGetExtra(Types.HumanoidAnimationType.SamplerType.ExtraName, context.Container.JsonSchemas, out samplerExtra)) + { + throw new NotImplementedException($"Extras {Types.HumanoidAnimationType.SamplerType.ExtraName} is not given"); + } + var timestamps = ImportTimestamp(context, sampler.Input); - var values = ImportHumanoidValue(context, sampler.Output); + var values = ImportHumanoidFloatScalarValue(context, sampler.Output); Debug.Assert(timestamps.Length == values.Length); + var inTangents = ImportHumanoidFloatScalarValue(context, samplerExtra.InTangent); + var inWeights = ImportHumanoidFloatScalarValue(context, samplerExtra.InWeight); + var outTangents = ImportHumanoidFloatScalarValue(context, samplerExtra.OutTangent); + var outWeights = ImportHumanoidFloatScalarValue(context, samplerExtra.OutWeight); + // TODO: support interpolation var keyframes = new Keyframe[timestamps.Length]; for (var i = 0; i < timestamps.Length; ++i) { - keyframes[i] = new Keyframe(timestamps[i], values[i]); + var keyframe = new Keyframe( + time: timestamps[i], + value: values[i], + inTangent: inTangents[i], + outTangent: outTangents[i], + inWeight: inWeights[i], + outWeight: outWeights[i]); + keyframes[i] = keyframe; } var curve = new AnimationCurve(keyframes); - animClip.SetCurve("", typeof(Animator), extra.PropertyName, curve); + animClip.SetCurve(channelExtra.RelativePath, typeof(Animator), channelExtra.PropertyName, curve); } + _clips.Add(animClip); // mutate the container + return Task.FromResult(resource); } @@ -77,7 +105,7 @@ public static float[] ImportTimestamp(IImporterContext context, int index) throw new NotImplementedException(); // TODO } - public static float[] ImportHumanoidValue(IImporterContext context, int index) + public static float[] ImportHumanoidFloatScalarValue(IImporterContext context, int index) { // https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#_animation_sampler_input diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs index 4f23ab9..c644dd9 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/Types/HumanoidAnimationType.cs @@ -5,7 +5,9 @@ // file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) // +using VGltf.Types; using VJson; +using VJson.Schema; namespace VGltf.Unity.Ext.Helper.Types { @@ -16,11 +18,36 @@ public sealed class HumanoidAnimationType { public static readonly string ExtraName = "VGLTF_unity_humanoid_animation"; + [Json] + public sealed class SamplerType + { + public static readonly string ExtraName = "VGLTF_unity_humanoid_animation_sampler"; + + [JsonField(Name = "inTangent")] + [JsonSchemaRequired, JsonSchemaRef(typeof(GltfID))] + public int InTangent; // Accessor (Scalar, FLOAT) + + [JsonField(Name = "inWeight")] + [JsonSchemaRequired, JsonSchemaRef(typeof(GltfID))] + public int InWeight; // Accessor (Scalar, FLOAT) + + [JsonField(Name = "outTangent")] + [JsonSchemaRequired, JsonSchemaRef(typeof(GltfID))] + public int OutTangent; // Accessor (Scalar, FLOAT) + + [JsonField(Name = "outWeight")] + [JsonSchemaRequired, JsonSchemaRef(typeof(GltfID))] + public int OutWeight; // Accessor (Scalar, FLOAT) + } + [Json] public sealed class ChannelType { public static readonly string ExtraName = "VGLTF_unity_humanoid_animation_channel"; + [JsonField(Name = "relativePath")] + public string RelativePath; + [JsonField(Name = "propertyName")] public string PropertyName; } diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/ImporterRuntimeResources.cs b/Packages/net.yutopp.vgltf.unity/Runtime/ImporterRuntimeResources.cs index 0556eb5..6a7369e 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/ImporterRuntimeResources.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/ImporterRuntimeResources.cs @@ -17,7 +17,7 @@ public sealed class ImporterRuntimeResources : IDisposable public IndexedResourceDict Textures = new IndexedResourceDict(); public IndexedResourceDict Materials = new IndexedResourceDict(); public IndexedResourceDict Meshes = new IndexedResourceDict(); - public IndexedResourceDict Animations = new IndexedResourceDict(); + public IndexedDisposableResourceDict Animations = new IndexedDisposableResourceDict(); public Dictionary AuxResources { get; } = new Dictionary(); public void Dispose() diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResource.cs b/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResource.cs index 68e99d3..df31c3a 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResource.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResource.cs @@ -7,7 +7,7 @@ namespace VGltf.Unity { - public class IndexedResource + public sealed class IndexedResource { public int Index; public T Value; diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResourceDict.cs b/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResourceDict.cs index db5187e..28a0814 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResourceDict.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResourceDict.cs @@ -13,6 +13,92 @@ namespace VGltf.Unity { public sealed class IndexedResourceDict : IDisposable where V : UnityEngine.Object + { + readonly IndexedDisposableResourceDict> _internal = + new IndexedDisposableResourceDict>(); + + public IndexedResource Add(K k, int index, string name, V v) + { + var res = _internal.Add(k, index, name, new Utils.DestroyOnDispose(v)); + return Unwrap(res); + } + + public IndexedResource this[K k] + { + get => Unwrap(_internal[k]); + } + + public IndexedResource GetOrCall(K k, Func> generator) + { + return Unwrap(_internal.GetOrCall(k, () => Wrap(generator()))); + } + + public async Task> GetOrCallAsync(K k, Func>> generator) + { + return Unwrap(await _internal.GetOrCallAsync(k, async () => Wrap(await generator()))); + } + + public IEnumerable Map(Func, T> f) + { + return _internal.Map(wres => f(Unwrap(wres))); + } + + public bool Contains(K k) + { + return _internal.Contains(k); + } + + public bool TryGetValue(K k, out IndexedResource res) + { + var found = _internal.TryGetValue(k, out var wres); + if (found) + { + res = Unwrap(wres); + return true; + } + + res = default; + return false; + } + + public bool TryGetValueByName(string k, out IndexedResource res) + { + var found = _internal.TryGetValueByName(k, out var wres); + if (found) + { + res = Unwrap(wres); + return true; + } + + res = default; + return false; + } + + public void Dispose() + { + _internal.Dispose(); + } + + static IndexedResource> Wrap(IndexedResource wres) + { + return new IndexedResource> + { + Index = wres.Index, + Value = new Utils.DestroyOnDispose(wres.Value), + }; + } + + static IndexedResource Unwrap(IndexedResource> wres) + { + return new IndexedResource + { + Index = wres.Index, + Value = wres.Value.Value, + }; + } + } + + public sealed class IndexedDisposableResourceDict : IDisposable where V : IDisposable { readonly Dictionary> _dict = new Dictionary>(); readonly Dictionary> _nameDict = new Dictionary>(); @@ -90,8 +176,9 @@ public void Dispose() { foreach (var v in _dict.Values) { - Utils.Destroy(v.Value); + v.Value.Dispose(); } + _dict.Clear(); _nameDict.Clear(); } From 35c055caa32888d8005ecaf167596fbb5c583a87 Mon Sep 17 00:00:00 2001 From: yutopp Date: Mon, 6 Jun 2022 04:48:13 +0900 Subject: [PATCH 07/11] Update VRM examples to follow new interfaces for Animations --- .../VRMExample/Scripts/VRMLoader.cs | 80 ++++++++++++------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs b/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs index ef1340d..90794d8 100644 --- a/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs +++ b/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs @@ -22,9 +22,6 @@ public sealed class VRMLoader : MonoBehaviour [SerializeField] public RuntimeAnimatorController RuntimeAnimatorController; - // デバッグ用 - [SerializeField] AnimationClip[] Clips; - sealed class VRMResource : IDisposable { public IImporterContext Context; @@ -42,6 +39,21 @@ public void Dispose() readonly List _vrmResources = new List(); + [Serializable] + sealed class ClipResource : IDisposable + { + public IImporterContext Context; + [SerializeField] public AnimationClip[] ClipRefs; + + public void Dispose() + { + Context?.Dispose(); + } + } + + // デバッグ用 + [SerializeField] List _clipResources = new List(); + void Start() { loadButton.onClick.AddListener(UIOnLoadButtonClick); @@ -61,6 +73,11 @@ void OnDestroy() { disposable.Dispose(); } + + foreach(var disposable in _clipResources) + { + disposable.Dispose(); + } } async UniTask LoadVRM() @@ -122,9 +139,9 @@ void UIOnLoadButtonClick() async UniTaskVoid UIOnLoadButtonClickAsync() { -#if true - var context = await ImportClip("anim.glb"); // TODO: このままだとリソースリークするのでデバッグ終わったら消す - Clips = context.Resources.Animations.Map(clip => clip).Select(iv => iv.Value).ToArray(); +#if false + var clipRes = await ImportClip("anim.glb"); + _clipResources.Insert(0, clipRes); #endif var p0 = Common.MemoryProfile.Now; @@ -222,6 +239,33 @@ await Task.Run(() => #endif } + async Task ImportClip(string path) + { + // Read the glTF container (unity-independent) + var gltfContainer = await Task.Run(() => + { + using (var fs = new FileStream(path, FileMode.Open)) + { + return GltfContainer.FromGlb(fs); + } + }); + + var clipRefs = new List(); + + var timeSlicer = new Common.TimeSlicer(); + using (var gltfImporter = new Importer(gltfContainer, timeSlicer)) + { + gltfImporter.Context.Importers.Animations.AddHook(new VGltf.Unity.Ext.Helper.HumanoidAnimationImporter(clipRefs)); + + var context = await gltfImporter.ImportOnlyAnimations(System.Threading.CancellationToken.None); + return new ClipResource + { + Context = context, + ClipRefs = clipRefs.ToArray(), + }; + } + } + async Task ExportClip(AnimationClip clip, string path) { GltfContainer gltfContainer = null; @@ -244,30 +288,6 @@ await Task.Run(() => }); } - async Task ImportClip(string path) - { - // Read the glTF container (unity-independent) - var gltfContainer = await Task.Run(() => - { - using (var fs = new FileStream(path, FileMode.Open)) - { - return GltfContainer.FromGlb(fs); - } - }); - - // Create a glTF Importer for Unity. - // The resources will be cached in the internal Context of this Importer. - // Resources can be released by calling Dispose of the Importer (or the internal Context). - var timeSlicer = new Common.TimeSlicer(); - using (var gltfImporter = new Importer(gltfContainer, timeSlicer)) - { - gltfImporter.Context.Importers.Animations.AddHook(new VGltf.Unity.Ext.Helper.HumanoidAnimationImporter()); - - // Load the Scene. - return await gltfImporter.ImportOnlyAnimations(System.Threading.CancellationToken.None); - } - } - void DebugLogProfile(Common.MemoryProfile now, Common.MemoryProfile prev = null) { Debug.Log($"----------"); From 27f130f9c3b4184ad3c722e004198c8a04400922 Mon Sep 17 00:00:00 2001 From: yutopp Date: Mon, 6 Jun 2022 11:24:32 +0900 Subject: [PATCH 08/11] Remove Animation importers/exporters from standard features. Treat these features by helper hooks --- .../Runtime/AnimationExporter.cs | 49 ----------------- .../Runtime/AnimationImporter.cs | 51 ------------------ .../Runtime/Exporter.cs | 26 ++------- .../Extra/Helper/AnimationClipExporter.cs | 53 +++++++++++++++++++ .../Helper/AnimationClipExporter.cs.meta} | 2 +- .../Extra/Helper/AnimationClipImporter.cs | 53 +++++++++++++++++++ .../Helper/AnimationClipImporter.cs.meta} | 2 +- .../Extra/Helper/HumanoidAnimationExporter.cs | 2 +- .../Extra/Helper/HumanoidAnimationImporter.cs | 17 ++---- .../Runtime/IExporterContext.cs | 1 - .../Runtime/IImporterContext.cs | 1 - .../Runtime/Importer.cs | 11 +--- .../Runtime/IndexedResource.cs | 12 +++-- .../Runtime/IndexedResourceDict.cs | 24 ++++----- 14 files changed, 138 insertions(+), 166 deletions(-) delete mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs delete mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipExporter.cs rename Packages/net.yutopp.vgltf.unity/Runtime/{AnimationExporter.cs.meta => Extra/Helper/AnimationClipExporter.cs.meta} (83%) create mode 100644 Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipImporter.cs rename Packages/net.yutopp.vgltf.unity/Runtime/{AnimationImporter.cs.meta => Extra/Helper/AnimationClipImporter.cs.meta} (83%) diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs deleted file mode 100644 index 20349a8..0000000 --- a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright (c) 2022- yutopp (yutopp@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) -// - -using System; -using UnityEngine; - -namespace VGltf.Unity -{ - public abstract class AnimationExporterHook - { - public abstract IndexedResource Export(IExporterContext context, AnimationClip clip); - } - - public sealed class AnimationExporter : ExporterRefHookable - { - public override IExporterContext Context { get; } - - public AnimationExporter(IExporterContext context) - { - Context = context; - } - - public IndexedResource Export(AnimationClip clip) - { - return Context.Resources.Animations.GetOrCall(clip, () => - { - return ForceExport(clip); - }); - } - - public IndexedResource ForceExport(AnimationClip clip) - { - foreach (var h in Hooks) - { - var r = h.Export(Context, clip); - if (r != null) - { - return r; - } - } - - throw new NotImplementedException(); - } - } -} diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs deleted file mode 100644 index 4983ca7..0000000 --- a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationImporter.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) 2022- yutopp (yutopp@gmail.com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) -// - -using System; -using System.Threading; -using System.Threading.Tasks; -using UnityEngine; - -namespace VGltf.Unity -{ - public abstract class AnimationImporterHook - { - public abstract Task> Import(IImporterContext context, int animIndex, CancellationToken ct); - } - - public sealed class AnimationImporter : ImporterRefHookable - { - public override IImporterContext Context { get; } - - public AnimationImporter(IImporterContext context) - { - Context = context; - } - - public async Task> Import(int animIndex, CancellationToken ct) - { - return await Context.Resources.Animations.GetOrCallAsync(animIndex, async () => - { - return await ForceImport(animIndex, ct); - }); - } - - public async Task> ForceImport(int animIndex, CancellationToken ct) - { - foreach (var h in Hooks) - { - var r = await h.Import(Context, animIndex, ct); - if (r != null) - { - return r; - } - } - - throw new NotImplementedException(); - } - } -} diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Exporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Exporter.cs index 14ec5b1..644fa9d 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/Exporter.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Exporter.cs @@ -69,7 +69,6 @@ public InnerContext(Config config) Materials = new MaterialExporter(this, materialExporterConfig), Textures = new TextureExporter(this), Images = new ImageExporter(this), - Animations = new AnimationExporter(this), }; } @@ -103,33 +102,31 @@ public Exporter(Config config = null) }; } - public void ExportGameObjectAsScene(GameObject go, AnimationClip[] clips = null) + public void ExportGameObjectAsScene(GameObject go) { if (_config.UseNormalizedTransforms) { using (var normalizer = new VGltf.Unity.Ext.TransformNormalizer()) { normalizer.Normalize(go); - ExportGameObjectAsSceneWithoutNormalize(normalizer.Go, clips); + ExportGameObjectAsSceneWithoutNormalize(normalizer.Go); } } else { - ExportGameObjectAsSceneWithoutNormalize(go, clips); + ExportGameObjectAsSceneWithoutNormalize(go); } } - public void ExportOnlyAnimationClips(AnimationClip[] clips) + public void ExportEmpty() { - ExportAnimationClipsInternal(clips); - foreach (var hook in Hooks) { hook.PostHook(this, null); } } - void ExportGameObjectAsSceneWithoutNormalize(GameObject go, AnimationClip[] clips) + void ExportGameObjectAsSceneWithoutNormalize(GameObject go) { Func[]> generator = () => { @@ -156,25 +153,12 @@ void ExportGameObjectAsSceneWithoutNormalize(GameObject go, AnimationClip[] clip }); Context.Gltf.Scene = rootSceneIndex; - if (clips != null) - { - ExportAnimationClipsInternal(clips); - } - foreach (var hook in Hooks) { hook.PostHook(this, go); } } - void ExportAnimationClipsInternal(AnimationClip[] clips) - { - foreach(var clip in clips) - { - Context.Exporters.Animations.Export(clip); - } - } - public GltfContainer IntoGlbContainer() { // Buffer diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipExporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipExporter.cs new file mode 100644 index 0000000..c2c20cc --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipExporter.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) 2022- yutopp (yutopp@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using VGltf.Types.Extensions; + +namespace VGltf.Unity.Ext.Helper +{ + public abstract class AnimationClipExporterHook + { + public abstract IndexedResource Export(IExporterContext context, AnimationClip clip); + } + + public sealed class AnimationClipExporter : ExporterHookBase + { + readonly AnimationClip[] _clips; + readonly AnimationClipExporterHook[] _hooks; + + public AnimationClipExporter(AnimationClip clip, AnimationClipExporterHook[] hooks) + : this(new AnimationClip[] { clip }, hooks) + { + } + + public AnimationClipExporter(AnimationClip[] clips, AnimationClipExporterHook[] hooks) + { + _clips = clips; + _hooks = hooks; + } + + public override void PostHook(Exporter exporter, GameObject go) + { + foreach (var clip in _clips) + { + foreach (var hook in _hooks) + { + var r = hook.Export(exporter.Context, clip); + if (r != null) + { + // If the clip was processed, ignore hooks subsequently (per clips). + break; + } + } + } + } + } +} diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs.meta b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipExporter.cs.meta similarity index 83% rename from Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs.meta rename to Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipExporter.cs.meta index f9f5d83..e1648b3 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/AnimationExporter.cs.meta +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipExporter.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 758df19b3e6374b5eacfc0e223240ff3 +guid: ffc50ae30f40e46b584a695d0993ad03 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipImporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipImporter.cs new file mode 100644 index 0000000..871b51a --- /dev/null +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/AnimationClipImporter.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) 2022- yutopp (yutopp@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; + +namespace VGltf.Unity.Ext.Helper +{ + public abstract class AnimationClipImporterHook + { + public abstract Task> Import(IImporterContext context, int animIndex, CancellationToken ct); + } + + public sealed class AnimationClipImporter : ImporterHookBase + { + readonly AnimationClipImporterHook[] _hooks; + readonly List _clipRefs; + + public AnimationClipImporter(AnimationClipImporterHook[] hooks, List clipRefs) + { + _hooks = hooks; + _clipRefs = clipRefs; + } + + public override async Task PostHook(IImporterContext context, CancellationToken ct) + { + var gltf = context.Container.Gltf; + + for(var i=0; i Export(IExporterContext context, AnimationClip clip) diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs index 3995ae6..bd0a640 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationImporter.cs @@ -13,16 +13,9 @@ namespace VGltf.Unity.Ext.Helper { - public sealed class HumanoidAnimationImporter : AnimationImporterHook + public sealed class HumanoidAnimationImporter : AnimationClipImporterHook { - readonly List _clips; - - public HumanoidAnimationImporter(List clips) - { - _clips = clips; - } - - public override Task> Import(IImporterContext context, int animIndex, CancellationToken ct) + public override Task> Import(IImporterContext context, int animIndex, CancellationToken ct) { var gltf = context.Container.Gltf; var gltfAnim = gltf.Animations[animIndex]; @@ -38,6 +31,8 @@ public override Task> Import(IImporterContext conte animClip.name = gltfAnim.Name; var resource = context.Resources.Animations.Add(animIndex, animIndex, animClip.name, new Utils.DestroyOnDispose(animClip)); + // Ownership is already held by "context.Resources". This is just a reference. + var resourceTyped = new IndexedResource(resource.Index, animClip); foreach (var channel in gltfAnim.Channels) { @@ -82,9 +77,7 @@ public override Task> Import(IImporterContext conte animClip.SetCurve(channelExtra.RelativePath, typeof(Animator), channelExtra.PropertyName, curve); } - _clips.Add(animClip); // mutate the container - - return Task.FromResult(resource); + return Task.FromResult(resourceTyped); } public static float[] ImportTimestamp(IImporterContext context, int index) diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/IExporterContext.cs b/Packages/net.yutopp.vgltf.unity/Runtime/IExporterContext.cs index 0bb5fab..38fbad4 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/IExporterContext.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/IExporterContext.cs @@ -27,6 +27,5 @@ public sealed class ResourceExporters public MaterialExporter Materials; public TextureExporter Textures; public ImageExporter Images; - public AnimationExporter Animations; } } diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/IImporterContext.cs b/Packages/net.yutopp.vgltf.unity/Runtime/IImporterContext.cs index a1e0583..be80b57 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/IImporterContext.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/IImporterContext.cs @@ -29,7 +29,6 @@ public sealed class ResourceImporters public MaterialImporter Materials; public TextureImporter Textures; public ImageImporter Images; - public AnimationImporter Animations; } public sealed class ImportingSetting diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Importer.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Importer.cs index c057010..88d5121 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/Importer.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Importer.cs @@ -95,7 +95,6 @@ public InnerContext(GltfContainer container, IResourceLoader loader, ITimeSlicer Materials = new MaterialImporter(this, materialImporterConfig), Textures = new TextureImporter(this), Images = new ImageImporter(this), - Animations = new AnimationImporter(this), }; } @@ -171,16 +170,8 @@ public async Task ImportSceneNodes(CancellationToken ct) return TakeContext(); } - public async Task ImportOnlyAnimations(CancellationToken ct) + public async Task ImportEmpty(CancellationToken ct) { - var gltf = Context.Container.Gltf; - - for(var i=0; i { - public int Index; - public T Value; + public readonly int Index; + public readonly T Value; + + public IndexedResource(int index, T value) + { + Index = index; + Value = value; + } } -} \ No newline at end of file +} diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResourceDict.cs b/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResourceDict.cs index 28a0814..0b988f8 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResourceDict.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/IndexedResourceDict.cs @@ -81,20 +81,18 @@ public void Dispose() static IndexedResource> Wrap(IndexedResource wres) { - return new IndexedResource> - { - Index = wres.Index, - Value = new Utils.DestroyOnDispose(wres.Value), - }; + return new IndexedResource>( + index: wres.Index, + value: new Utils.DestroyOnDispose(wres.Value) + ); } static IndexedResource Unwrap(IndexedResource> wres) { - return new IndexedResource - { - Index = wres.Index, - Value = wres.Value.Value, - }; + return new IndexedResource( + index: wres.Index, + value: wres.Value.Value + ); } } @@ -105,11 +103,7 @@ public sealed class IndexedDisposableResourceDict : IDisposable where V : public IndexedResource Add(K k, int index, string name, V v) { - var resource = new IndexedResource - { - Index = index, - Value = v, - }; + var resource = new IndexedResource(index, v); _dict.Add(k, resource); if (!string.IsNullOrEmpty(name)) From 6f83bf0274c0abb3370dfa074516505c892e7c1b Mon Sep 17 00:00:00 2001 From: yutopp Date: Mon, 6 Jun 2022 11:24:54 +0900 Subject: [PATCH 09/11] Update VRM examples to follow new interfaces for Animations --- .../VRMExample/Scripts/VRMLoader.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs b/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs index 90794d8..7831d8a 100644 --- a/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs +++ b/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs @@ -234,7 +234,7 @@ await Task.Run(() => Debug.Log("exported"); }); -#if true +#if false await ExportClip(clip, "anim.glb"); #endif } @@ -255,9 +255,14 @@ async Task ImportClip(string path) var timeSlicer = new Common.TimeSlicer(); using (var gltfImporter = new Importer(gltfContainer, timeSlicer)) { - gltfImporter.Context.Importers.Animations.AddHook(new VGltf.Unity.Ext.Helper.HumanoidAnimationImporter(clipRefs)); - - var context = await gltfImporter.ImportOnlyAnimations(System.Threading.CancellationToken.None); + gltfImporter.AddHook(new VGltf.Unity.Ext.Helper.AnimationClipImporter( + hooks: new VGltf.Unity.Ext.Helper.AnimationClipImporterHook[] { + new VGltf.Unity.Ext.Helper.HumanoidAnimationImporter(), + }, + clipRefs: clipRefs + )); + + var context = await gltfImporter.ImportEmpty(System.Threading.CancellationToken.None); return new ClipResource { Context = context, @@ -271,9 +276,14 @@ async Task ExportClip(AnimationClip clip, string path) GltfContainer gltfContainer = null; using (var gltfExporter = new Exporter()) { - gltfExporter.Context.Exporters.Animations.AddHook(new VGltf.Unity.Ext.Helper.HumanoidAnimationExporter()); - - gltfExporter.ExportOnlyAnimationClips(new AnimationClip[] { clip }); + gltfExporter.AddHook(new VGltf.Unity.Ext.Helper.AnimationClipExporter( + clips: new AnimationClip[] { clip }, + hooks: new VGltf.Unity.Ext.Helper.AnimationClipExporterHook[] { + new VGltf.Unity.Ext.Helper.HumanoidAnimationExporter(), + } + )); + + gltfExporter.ExportEmpty(); gltfContainer = gltfExporter.IntoGlbContainer(); } From 60bbedef38cd5fa2a7e6eefee65c2ad7db5e7083 Mon Sep 17 00:00:00 2001 From: yutopp Date: Mon, 6 Jun 2022 13:15:26 +0900 Subject: [PATCH 10/11] Generated glTFs that include animations now valid format --- .../Extra/Helper/HumanoidAnimationExporter.cs | 26 ++++++++++++------- .../Runtime/Types/Animation.cs | 22 +++++----------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs index 69f27ac..157351a 100644 --- a/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs +++ b/Packages/net.yutopp.vgltf.unity/Runtime/Extra/Helper/HumanoidAnimationExporter.cs @@ -82,6 +82,15 @@ public override IndexedResource Export(IExporterContext context, }); samplers.Add(sampler); + // node (dummy) + var dummyNode = context.Gltf.AddNode(new VGltf.Types.Node + { + Matrix = null, + Translation = null, + Rotation = null, + Scale = null, + }); + // channel var channel = new VGltf.Types.Animation.ChannelType { @@ -89,7 +98,8 @@ public override IndexedResource Export(IExporterContext context, // NOTE: Target will not be used when "VGLTF_unity_humanoid_animation_channel" specified Target = new VGltf.Types.Animation.ChannelType.TargetType { - Path = VGltf.Types.Animation.ChannelType.TargetType.PathEnum.Translation, + Node = dummyNode, // Specify the dummy node to suppress validation errors... + Path = "", // Never used for Unity humanoid }, }; channel.AddExtra(Types.HumanoidAnimationType.ChannelType.ExtraName, new Types.HumanoidAnimationType.ChannelType @@ -129,10 +139,7 @@ public static int ExportTimestamp(IExporterContext context, float[] timestamps) // Scalar | FLOAT byte[] buffer = PrimitiveExporter.Marshal(timestamps); - var viewIndex = context.BufferBuilder.AddView( - new ArraySegment(buffer), - null, - VGltf.Types.BufferView.TargetEnum.ELEMENT_ARRAY_BUFFER); + var viewIndex = context.BufferBuilder.AddView(new ArraySegment(buffer)); var viewComponentType = VGltf.Types.Accessor.ComponentTypeEnum.FLOAT; @@ -142,6 +149,8 @@ public static int ExportTimestamp(IExporterContext context, float[] timestamps) ByteOffset = 0, ComponentType = viewComponentType, Count = timestamps.Length, + Min = new float[]{ Mathf.Min(timestamps) }, + Max = new float[]{ Mathf.Max(timestamps) }, Type = VGltf.Types.Accessor.TypeEnum.Scalar, }; return context.Gltf.AddAccessor(accessor); @@ -152,10 +161,7 @@ public static int ExportHumanoidFloatScalarValue(IExporterContext context, float // Scalar | FLOAT byte[] buffer = PrimitiveExporter.Marshal(values); - var viewIndex = context.BufferBuilder.AddView( - new ArraySegment(buffer), - null, - VGltf.Types.BufferView.TargetEnum.ELEMENT_ARRAY_BUFFER); + var viewIndex = context.BufferBuilder.AddView(new ArraySegment(buffer)); var viewComponentType = VGltf.Types.Accessor.ComponentTypeEnum.FLOAT; @@ -165,6 +171,8 @@ public static int ExportHumanoidFloatScalarValue(IExporterContext context, float ByteOffset = 0, ComponentType = viewComponentType, Count = values.Length, + Min = new float[]{ Mathf.Min(values) }, + Max = new float[]{ Mathf.Max(values) }, Type = VGltf.Types.Accessor.TypeEnum.Scalar, }; return context.Gltf.AddAccessor(accessor); diff --git a/Packages/net.yutopp.vgltf/Runtime/Types/Animation.cs b/Packages/net.yutopp.vgltf/Runtime/Types/Animation.cs index 146cd67..eba71f9 100644 --- a/Packages/net.yutopp.vgltf/Runtime/Types/Animation.cs +++ b/Packages/net.yutopp.vgltf/Runtime/Types/Animation.cs @@ -41,28 +41,18 @@ public sealed class ChannelType : GltfProperty [JsonSchema(Id = "animation.channel.target.schema.json")] public sealed class TargetType : GltfProperty { + public const string PathEnumTranslation = "translation"; + public const string PathEnumRotation = "rotation"; + public const string PathEnumScale = "scale"; + public const string PathEnumWeights = "weights"; + [JsonField(Name = "node"), JsonFieldIgnorable] [JsonSchemaRef(typeof(GltfID))] public int? Node; [JsonField(Name = "path")] [JsonSchemaRequired] - public PathEnum Path; - - // - - [Json(EnumConversion = EnumConversionType.AsString)] - public enum PathEnum - { - [JsonField(Name = "translation")] - Translation, - [JsonField(Name = "rotation")] - Rotation, - [JsonField(Name = "scale")] - Scale, - [JsonField(Name = "weights")] - Weights, - } + public string Path; } } From 88bbb31a9b6e97c07d1940f8a5fac73e779f1f75 Mon Sep 17 00:00:00 2001 From: yutopp Date: Mon, 6 Jun 2022 13:16:08 +0900 Subject: [PATCH 11/11] Fix FileMode --- Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs b/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs index 7831d8a..70b915c 100644 --- a/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs +++ b/Assets/VGltfExamples/VRMExample/Scripts/VRMLoader.cs @@ -226,7 +226,7 @@ async UniTaskVoid UIOnExportButtonClickedAsync() var filePath = outputFilePathInput.text; await Task.Run(() => { - using (var fs = new FileStream(filePath, FileMode.OpenOrCreate)) + using (var fs = new FileStream(filePath, FileMode.Create)) { GltfContainer.ToGlb(fs, gltfContainer); } @@ -289,7 +289,7 @@ async Task ExportClip(AnimationClip clip, string path) await Task.Run(() => { - using (var fs = new FileStream(path, FileMode.OpenOrCreate)) + using (var fs = new FileStream(path, FileMode.Create)) { GltfContainer.ToGlb(fs, gltfContainer); }