Browse Source

Added basic support for creating animations

Vicente Penades 6 years ago
parent
commit
0f2acf9612

+ 20 - 0
src/SharpGLTF.DOM/Schema2/gltf.Accessors.cs

@@ -175,6 +175,16 @@ namespace SharpGLTF.Schema2
             return this.WithVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);
             return this.WithVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);
         }
         }
 
 
+        public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Single> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        {
+            Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
+            Guard.MustBePositiveAndMultipleOf(ElementType.SCALAR.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
+
+            var array = new Memory.ScalarArray(buffer.Content.Slice(byteOffset), buffer.ByteStride);
+            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
+            return WithVertexData(buffer, byteOffset, items.Count, ElementType.SCALAR, encoding, normalized);
+        }
+
         public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Vector2> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Vector2> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
             Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
@@ -205,6 +215,16 @@ namespace SharpGLTF.Schema2
             return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC4, encoding, normalized);
             return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC4, encoding, normalized);
         }
         }
 
 
+        public Accessor WithVertexData(BufferView buffer, int byteOffset, IReadOnlyList<Quaternion> items, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
+        {
+            Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
+            Guard.MustBePositiveAndMultipleOf(ElementType.VEC4.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
+
+            var array = new Memory.QuaternionArray(buffer.Content.Slice(byteOffset), buffer.ByteStride);
+            Memory.EncodedArrayUtils.FillFrom(array, 0, items);
+            return WithVertexData(buffer, byteOffset, items.Count, ElementType.VEC4, encoding, normalized);
+        }
+
         public Accessor WithVertexData(BufferView buffer, int byteOffset, int itemCount, ElementType dimensions = ElementType.VEC3, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         public Accessor WithVertexData(BufferView buffer, int byteOffset, int itemCount, ElementType dimensions = ElementType.VEC3, ComponentType encoding = ComponentType.FLOAT, Boolean normalized = false)
         {
         {
             Guard.NotNull(buffer, nameof(buffer));
             Guard.NotNull(buffer, nameof(buffer));

+ 56 - 38
src/SharpGLTF.DOM/Schema2/gltf.Animations.cs

@@ -6,13 +6,14 @@ using System.Linq;
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
     using Collections;
     using Collections;
+    using System.Numerics;
 
 
     [System.Diagnostics.DebuggerDisplay("Animation[{LogicalIndex}] {Name}")]
     [System.Diagnostics.DebuggerDisplay("Animation[{LogicalIndex}] {Name}")]
     public sealed partial class Animation
     public sealed partial class Animation
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        public Animation()
+        internal Animation()
         {
         {
             _channels = new ChildrenCollection<AnimationChannel, Animation>(this);
             _channels = new ChildrenCollection<AnimationChannel, Animation>(this);
             _samplers = new ChildrenCollection<AnimationSampler, Animation>(this);
             _samplers = new ChildrenCollection<AnimationSampler, Animation>(this);
@@ -29,72 +30,83 @@ namespace SharpGLTF.Schema2
 
 
         internal IReadOnlyList<AnimationSampler> _Samplers => _samplers;
         internal IReadOnlyList<AnimationSampler> _Samplers => _samplers;
 
 
+        /// <summary>
+        /// Gets the list of <see cref="AnimationChannel"/> instances.
+        /// </summary>
         public IReadOnlyList<AnimationChannel> Channels => _channels;
         public IReadOnlyList<AnimationChannel> Channels => _channels;
 
 
         #endregion
         #endregion
 
 
         #region API
         #region API
 
 
-        public AnimationSampler CreateSampler(Accessor input, Accessor output, AnimationInterpolationMode interpolation)
+        public Accessor CreateInputAccessor(IReadOnlyList<Single> input)
         {
         {
-            var sampler = new AnimationSampler(input, output, interpolation);
+            var buffer = LogicalParent.UseBufferView(new Byte[input.Count * 4]);
+            var accessor = LogicalParent.CreateAccessor("Animation.Input")
+                .WithData(buffer, 0, input.Count, ElementType.SCALAR, ComponentType.FLOAT, false);
 
 
-            _samplers.Add(sampler);
+            Memory.EncodedArrayUtils.FillFrom(accessor.AsScalarArray(), 0, input);
 
 
-            return sampler;
+            accessor.UpdateBounds();
+
+            return accessor;
         }
         }
 
 
-        /*
-        public AnimationSampler CreateSampler(IReadOnlyList<Single> input, IReadOnlyList<Single> output, AnimationInterpolationMode interpolation)
+        public Accessor CreateOutputAccessor(IReadOnlyList<Vector3> output)
         {
         {
-            var inputData = input.ToArray().ToByteArray();
-            var outputData = output.ToArray().ToByteArray();
+            var buffer = LogicalParent.UseBufferView(new Byte[output.Count * 4 * 3]);
+            var accessor = LogicalParent.CreateAccessor("Animation.Output")
+                .WithData(buffer, 0, output.Count, ElementType.VEC3, ComponentType.FLOAT, false);
+
+            Memory.EncodedArrayUtils.FillFrom(accessor.AsVector3Array(), 0, output);
 
 
-            var inputAccessor = LogicalParent._CreateDataAccessor(inputData, Runtime.Encoding.DimensionType.Scalar, input.Count);
-            var outputAccesor = LogicalParent._CreateDataAccessor(outputData, Runtime.Encoding.DimensionType.Scalar, output.Count);
+            accessor.UpdateBounds();
 
 
-            return CreateSampler(inputAccessor, outputAccesor, interpolation);
+            return accessor;
         }
         }
 
 
-        public AnimationSampler CreateSampler(IReadOnlyList<Single> input, IReadOnlyList<System.Numerics.Vector3> output, AnimationInterpolationMode interpolation)
+        /// <summary>
+        /// Creates a new <see cref="AnimationSampler"/> instance and adds it to this <see cref="Animation"/>.
+        /// </summary>
+        /// <param name="input">An <see cref="Accessor"/> containing input (TIME) values.</param>
+        /// <param name="output">An <see cref="Accessor"/> containing output (TRS) values.</param>
+        /// <param name="interpolation">how the output values are interpolated.</param>
+        /// <returns>An <see cref="AnimationSampler"/> instance.</returns>
+        public AnimationSampler CreateSampler(Accessor input, Accessor output, AnimationInterpolationMode interpolation)
         {
         {
-            var inputData = input.ToArray().ToByteArray();
-            var outputData = output.ToArray().ToByteArray();
+            Guard.MustShareLogicalParent(this, input, nameof(input));
+            Guard.MustShareLogicalParent(this, output, nameof(output));
 
 
-            var inputAccessor = LogicalParent._CreateDataAccessor(inputData, Runtime.Encoding.DimensionType.Scalar, input.Count);
-            var outputAccesor = LogicalParent._CreateDataAccessor(outputData, Runtime.Encoding.DimensionType.Vector3, output.Count);
+            var sampler = new AnimationSampler(input, output, interpolation);
 
 
-            return CreateSampler(inputAccessor, outputAccesor, interpolation);
+            _samplers.Add(sampler);
+
+            return sampler;
         }
         }
 
 
-        public AnimationSampler CreateSampler(IReadOnlyList<Single> input, IReadOnlyList<System.Numerics.Quaternion> output, AnimationInterpolationMode interpolation)
+        public AnimationChannel CreateChannel(Node node, PathType path, AnimationSampler sampler)
         {
         {
-            var inputData = input.ToArray().ToByteArray();
-            var outputData = output.ToArray().ToByteArray();
+            Guard.MustShareLogicalParent(this, node, nameof(node));
+            Guard.NotNull(sampler, nameof(sampler));
+            Guard.IsTrue(Object.ReferenceEquals(this, sampler.LogicalParent), nameof(sampler));
 
 
-            var inputAccessor = LogicalParent._CreateDataAccessor(inputData, Runtime.Encoding.DimensionType.Scalar, input.Count);
-            var outputAccesor = LogicalParent._CreateDataAccessor(outputData, Runtime.Encoding.DimensionType.Vector4, output.Count);
-
-            return CreateSampler(inputAccessor, outputAccesor, interpolation);
-        }*/
-
-        public void AddChannel(Node node, PathType path, AnimationSampler sampler)
-        {
             var channel = new AnimationChannel(node, path, sampler);
             var channel = new AnimationChannel(node, path, sampler);
 
 
             _channels.Add(channel);
             _channels.Add(channel);
+
+            return channel;
         }
         }
 
 
         #endregion
         #endregion
     }
     }
 
 
-    public sealed partial class AnimationChannelTarget
+    sealed partial class AnimationChannelTarget
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        public AnimationChannelTarget() { }
+        internal AnimationChannelTarget() { }
 
 
-        public AnimationChannelTarget(Node targetNode, PathType targetPath)
+        internal AnimationChannelTarget(Node targetNode, PathType targetPath)
         {
         {
             _node = targetNode.LogicalIndex;
             _node = targetNode.LogicalIndex;
             _path = targetPath;
             _path = targetPath;
@@ -115,9 +127,9 @@ namespace SharpGLTF.Schema2
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        public AnimationChannel() { }
+        internal AnimationChannel() { }
 
 
-        public AnimationChannel(Node targetNode, PathType targetPath, AnimationSampler sampler)
+        internal AnimationChannel(Node targetNode, PathType targetPath, AnimationSampler sampler)
         {
         {
             _target = new AnimationChannelTarget(targetNode, targetPath);
             _target = new AnimationChannelTarget(targetNode, targetPath);
             _sampler = sampler.LogicalIndex;
             _sampler = sampler.LogicalIndex;
@@ -127,10 +139,16 @@ namespace SharpGLTF.Schema2
 
 
         #region properties
         #region properties
 
 
+        /// <summary>
+        /// Gets the <see cref="Animation"/> instance that owns this object.
+        /// </summary>
         public Animation LogicalParent { get; private set; }
         public Animation LogicalParent { get; private set; }
 
 
         void IChildOf<Animation>._SetLogicalParent(Animation parent) { LogicalParent = parent; }
         void IChildOf<Animation>._SetLogicalParent(Animation parent) { LogicalParent = parent; }
 
 
+        /// <summary>
+        /// Gets the <see cref="AnimationSampler"/> instance used by this <see cref="AnimationChannel"/>.
+        /// </summary>
         public AnimationSampler Sampler => this.LogicalParent._Samplers[this._sampler];
         public AnimationSampler Sampler => this.LogicalParent._Samplers[this._sampler];
 
 
         public Node TargetNode
         public Node TargetNode
@@ -145,7 +163,7 @@ namespace SharpGLTF.Schema2
 
 
         public PathType TargetNodePath => this._target?._NodePath ?? PathType.translation;
         public PathType TargetNodePath => this._target?._NodePath ?? PathType.translation;
 
 
-        public int OutputStride
+        public int OutputByteStride
         {
         {
             get
             get
             {
             {
@@ -167,11 +185,11 @@ namespace SharpGLTF.Schema2
     {
     {
         #region lifecycle
         #region lifecycle
 
 
-        public AnimationSampler() { }
+        internal AnimationSampler() { }
 
 
-        public AnimationSampler(Accessor input, Accessor output, AnimationInterpolationMode interpolation)
+        internal AnimationSampler(Accessor input, Accessor output, AnimationInterpolationMode interpolation)
         {
         {
-            _interpolation = interpolation;
+            _interpolation = interpolation.AsNullable(_interpolationDefault);
             _input = input.LogicalIndex;
             _input = input.LogicalIndex;
             _output = output.LogicalIndex;
             _output = output.LogicalIndex;
         }
         }

+ 45 - 0
tests/SharpGLTF.Tests/Schema2/Authoring/CreateModelTests.cs

@@ -211,6 +211,51 @@ namespace SharpGLTF.Schema2.Authoring
             // fill our node with the mesh
             // fill our node with the mesh
             meshBuilder.CopyToNode(rnode, createMaterialForColor);
             meshBuilder.CopyToNode(rnode, createMaterialForColor);
 
 
+            model.AttachToCurrentTest("result.glb");
+            model.AttachToCurrentTest("result.gltf");
+        }
+
+        [Test(Description = "Creates an animated scene using a mesh builder helper class")]
+        public void CreateAnimatedMeshBuilderScene()
+        {
+            TestContext.CurrentContext.AttachShowDirLink();
+            TestContext.CurrentContext.AttachGltfValidatorLink();
+
+            var meshBuilder = new InterleavedMeshBuilder<myVertex, Vector4>();
+
+            var v1 = new myVertex(-10, 10, 0, -10, 10, 15);
+            var v2 = new myVertex(10, 10, 0, 10, 10, 15);
+            var v3 = new myVertex(10, -10, 0, 10, -10, 15);
+            var v4 = new myVertex(-10, -10, 0, -10, -10, 15);
+            meshBuilder.AddPolygon(new Vector4(1, 1, 1, 1), v1, v2, v3, v4);
+
+            var model = ModelRoot.CreateModel();
+            var scene = model.UseScene("Default");
+            var rnode = scene.CreateNode("RootNode");
+            rnode.LocalTransform = new Transforms.AffineTransform(null, null, null, Vector3.Zero);
+
+            // setup a lambda function that creates a material for a given color
+            Material createMaterialForColor(Vector4 color)
+            {
+                var material = model.CreateMaterial().WithDefault(color);
+                material.DoubleSided = true;
+                return material;
+            };
+
+            // fill our node with the mesh
+            meshBuilder.CopyToNode(rnode, createMaterialForColor);
+
+            var animation = model.CreateAnimation("Animation");
+            var asampler = animation.CreateSampler
+                (
+                animation.CreateInputAccessor( new float[] { 1, 2, 3, 4 } ),
+                animation.CreateOutputAccessor( new[] { new Vector3(0, 0, 0), new Vector3(50, 0, 0), new Vector3(0, 50, 0), new Vector3(0, 0, 0) } ),
+                AnimationInterpolationMode.LINEAR
+                );
+
+            animation.CreateChannel(rnode, PathType.translation, asampler);
+            
+
             model.AttachToCurrentTest("result.glb");
             model.AttachToCurrentTest("result.glb");
             model.AttachToCurrentTest("result.gltf");
             model.AttachToCurrentTest("result.gltf");
         }
         }