Forráskód Böngészése

Refactor buffer handling when exporting meshes
Export node animations - Translation Rotation Scale

noalak 8 éve
szülő
commit
fcdfef8dd0

+ 12 - 0
Exporters/3ds Max/GltfExport.Entities/GLTF.cs

@@ -46,6 +46,9 @@ namespace GLTFExport.Entities
         [DataMember(EmitDefaultValue = false)]
         public GLTFSampler[] samplers { get; set; }
 
+        [DataMember(EmitDefaultValue = false)]
+        public GLTFAnimation[] animations { get; set; }
+
         public string OutputFolder { get; private set; }
         public string OutputFile { get; private set; }
 
@@ -59,6 +62,7 @@ namespace GLTFExport.Entities
         public List<GLTFTexture> TexturesList { get; private set; }
         public List<GLTFImage> ImagesList { get; private set; }
         public List<GLTFSampler> SamplersList { get; private set; }
+        public List<GLTFAnimation> AnimationsList { get; private set; }
 
         public GLTFBuffer buffer;
         public GLTFBufferView bufferViewScalar;
@@ -66,6 +70,9 @@ namespace GLTFExport.Entities
         public GLTFBufferView bufferViewFloatVec4;
         public GLTFBufferView bufferViewFloatVec2;
         public GLTFBufferView bufferViewImage;
+        public GLTFBufferView bufferViewAnimationFloatScalar;
+        public GLTFBufferView bufferViewAnimationFloatVec3;
+        public GLTFBufferView bufferViewAnimationFloatVec4;
 
         public GLTF(string outputPath)
         {
@@ -82,6 +89,7 @@ namespace GLTFExport.Entities
             TexturesList = new List<GLTFTexture>();
             ImagesList = new List<GLTFImage>();
             SamplersList = new List<GLTFSampler>();
+            AnimationsList = new List<GLTFAnimation>();
         }
 
         public void Prepare()
@@ -130,6 +138,10 @@ namespace GLTFExport.Entities
             {
                 samplers = SamplersList.ToArray();
             }
+            if (AnimationsList.Count > 0)
+            {
+                animations = AnimationsList.ToArray();
+            }
         }
     }
 }

+ 3 - 0
Exporters/3ds Max/GltfExport.Entities/GLTFAccessor.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Runtime.Serialization;
 
 namespace GLTFExport.Entities
@@ -56,6 +57,8 @@ namespace GLTFExport.Entities
 
         public GLTFBufferView BufferView;
 
+        public List<byte> bytesList = new List<byte>();
+
         public int getByteLength()
         {
             return count * getElementSize();

+ 22 - 0
Exporters/3ds Max/GltfExport.Entities/GLTFAnimation.cs

@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace GLTFExport.Entities
+{
+    [DataContract]
+    public class GLTFAnimation : GLTFChildRootProperty
+    {
+        /// <summary>
+        /// An array of channels, each of which targets an animation's sampler at a node's property.
+        /// Different channels of the same animation can't have equal targets.
+        /// </summary>
+        [DataMember(IsRequired = true)]
+        public GLTFChannel[] channels { get; set; }
+
+        /// <summary>
+        /// An array of samplers that combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target).
+        /// </summary>
+        [DataMember(IsRequired = true)]
+        public GLTFAnimationSampler[] samplers { get; set; }
+    }
+}

+ 49 - 0
Exporters/3ds Max/GltfExport.Entities/GLTFAnimationSampler.cs

@@ -0,0 +1,49 @@
+using System.Runtime.Serialization;
+
+namespace GLTFExport.Entities
+{
+    [DataContract]
+    public class GLTFAnimationSampler : GLTFProperty
+    {
+        public enum Interpolation
+        {
+            LINEAR,
+            STEP,
+            CATMULLROMSPLINE,
+            CUBICSPLINE
+        }
+
+        /// <summary>
+        /// The index of an accessor containing keyframe input values, e.g., time. That accessor must have componentType FLOAT.
+        /// The values represent time in seconds with time[0] >= 0.0, and strictly increasing values, i.e., time[n + 1] > time[n].
+        /// </summary>
+        [DataMember(IsRequired = true)]
+        public int input { get; set; }
+
+        [DataMember(EmitDefaultValue = false)]
+        public string interpolation { get; private set; }
+
+        /// <summary>
+        /// The index of an accessor containing keyframe output values.
+        /// When targeting TRS target, the accessor.componentType of the output values must be FLOAT.
+        /// When targeting morph weights, the accessor.componentType of the output values must be FLOAT
+        /// or normalized integer where each output element stores values with a count equal to the number of morph targets.
+        /// </summary>
+        [DataMember(IsRequired = true)]
+        public int output { get; set; }
+
+        public int index;
+
+        public void SetInterpolation(Interpolation interpolation)
+        {
+            this.interpolation = interpolation.ToString();
+        }
+
+        public GLTFAnimationSampler()
+        {
+            // For GLTF, default value is LINEAR
+            // but gltf loader of BABYLON doesn't handle missing interpolation value
+            SetInterpolation(Interpolation.LINEAR);
+        }
+    }
+}

+ 2 - 1
Exporters/3ds Max/GltfExport.Entities/GLTFBuffer.cs

@@ -12,6 +12,7 @@ namespace GLTFExport.Entities
         [DataMember(IsRequired = true)]
         public int byteLength { get; set; }
 
-        public List<byte> bytesList;
+        public List<byte> bytesList = new List<byte>();
+        public List<GLTFBufferView> BufferViews = new List<GLTFBufferView>();
     }
 }

+ 2 - 1
Exporters/3ds Max/GltfExport.Entities/GLTFBufferView.cs

@@ -16,9 +16,10 @@ namespace GLTFExport.Entities
         public int byteLength { get; set; }
 
         [DataMember(EmitDefaultValue = false)]
-        public int? byteStride { get; set; }
+        public int? byteStride { get; set; } // Field only defined for buffer views that contain vertex attributes.
 
         public GLTFBuffer Buffer;
+        public List<GLTFAccessor> Accessors = new List<GLTFAccessor>();
         public List<byte> bytesList = new List<byte>();
     }
 }

+ 21 - 0
Exporters/3ds Max/GltfExport.Entities/GLTFChannel.cs

@@ -0,0 +1,21 @@
+using System.Runtime.Serialization;
+
+namespace GLTFExport.Entities
+{
+    [DataContract]
+    public class GLTFChannel : GLTFProperty
+    {
+        /// <summary>
+        /// The index of a sampler in this animation used to compute the value for the target,
+        /// e.g., a node's translation, rotation, or scale (TRS).
+        /// </summary>
+        [DataMember(IsRequired = true)]
+        public int sampler { get; set; }
+
+        /// <summary>
+        /// The index of the node and TRS property to target.
+        /// </summary>
+        [DataMember(IsRequired = true)]
+        public GLTFChannelTarget target { get; set; }
+    }
+}

+ 20 - 0
Exporters/3ds Max/GltfExport.Entities/GLTFChannelTarget.cs

@@ -0,0 +1,20 @@
+using System.Runtime.Serialization;
+
+namespace GLTFExport.Entities
+{
+    [DataContract]
+    public class GLTFChannelTarget : GLTFProperty
+    {
+        /// <summary>
+        /// The index of the node to target.
+        /// </summary>
+        [DataMember(EmitDefaultValue = false)]
+        public int? node { get; set; }
+
+        /// <summary>
+        /// The name of the node's TRS property to modify, or the "weights" of the Morph Targets it instantiates.
+        /// </summary>
+        [DataMember(IsRequired = true)]
+        public string path { get; set; }
+    }
+}

+ 4 - 0
Exporters/3ds Max/GltfExport.Entities/GLTFExport.Entities.csproj

@@ -42,6 +42,10 @@
   <ItemGroup>
     <Compile Include="GLTF.cs" />
     <Compile Include="GLTFAccessor.cs" />
+    <Compile Include="GLTFAnimationSampler.cs" />
+    <Compile Include="GLTFAnimation.cs" />
+    <Compile Include="GLTFChannelTarget.cs" />
+    <Compile Include="GLTFChannel.cs" />
     <Compile Include="GLTFBufferView.cs" />
     <Compile Include="GLTFBuffer.cs" />
     <Compile Include="GLTFCameraPerspective.cs" />

+ 6 - 0
Exporters/3ds Max/Max2Babylon/2015/Max2Babylon2015.csproj

@@ -218,6 +218,12 @@
     <Compile Include="..\Tools\WebServer.cs">
       <Link>Tools\WebServer.cs</Link>
     </Compile>
+    <Compile Include="..\Exporter\GLTFBufferService.cs">
+      <Link>Exporter\GLTFBufferService.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Animation.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Animation.cs</Link>
+    </Compile>
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
       <AutoGen>True</AutoGen>

+ 6 - 0
Exporters/3ds Max/Max2Babylon/2017/Max2Babylon2017.csproj

@@ -218,6 +218,12 @@
     <Compile Include="..\Tools\WebServer.cs">
       <Link>Tools\WebServer.cs</Link>
     </Compile>
+    <Compile Include="..\Exporter\GLTFBufferService.cs">
+      <Link>Exporter\GLTFBufferService.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Animation.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Animation.cs</Link>
+    </Compile>
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
       <AutoGen>True</AutoGen>

+ 3 - 0
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.AbstractMesh.cs

@@ -55,6 +55,9 @@ namespace Max2Babylon
                 gltfNode.mesh = gltfMesh.index;
             }
 
+            // Animations
+            ExportNodeAnimation(babylonAbstractMesh, gltf, gltfNode);
+
             return gltfNode;
         }
     }

+ 159 - 0
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Animation.cs

@@ -0,0 +1,159 @@
+using BabylonExport.Entities;
+using GLTFExport.Entities;
+using System;
+using System.Collections.Generic;
+
+namespace Max2Babylon
+{
+    partial class BabylonExporter
+    {
+        private GLTFAnimation ExportNodeAnimation(BabylonAbstractMesh babylonAbstractMesh, GLTF gltf, GLTFNode gltfNode)
+        {
+            if (babylonAbstractMesh.animations != null && babylonAbstractMesh.animations.Length > 0)
+            {
+                RaiseMessage("GLTFExporter.Animation | Export animation of mesh named: " + babylonAbstractMesh.name, 2);
+
+                var channelList = new List<GLTFChannel>();
+                var samplerList = new List<GLTFAnimationSampler>();
+
+                foreach (BabylonAnimation babylonAnimation in babylonAbstractMesh.animations)
+                {
+                    // Target
+                    var gltfTarget = new GLTFChannelTarget
+                    {
+                        node = gltfNode.index
+                    };
+                    gltfTarget.path = _getTargetPath(babylonAnimation.property);
+                    if (gltfTarget.path == null)
+                    {
+                        // Unkown babylon animation property
+                        RaiseWarning("GLTFExporter.AbstractMesh | Unkown animation property '" + babylonAnimation.property + "'", 3);
+                        // Ignore this babylon animation
+                        continue;
+                    }
+
+                    // Buffer
+                    var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
+
+                    // --- Input ---
+                    var accessorInput = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
+                        "accessorAnimationInput",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.SCALAR
+                    );
+                    // Populate accessor
+                    accessorInput.min = new float[] { float.MaxValue };
+                    accessorInput.max = new float[] { float.MinValue };
+                    foreach (var babylonAnimationKey in babylonAnimation.keys)
+                    {
+                        var inputValue = babylonAnimationKey.frame / 60.0f; // TODO - Which FPS factor ?
+                        // Store values as bytes
+                        accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
+                        // Update min and max values
+                        GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
+                    };
+                    accessorInput.count = babylonAnimation.keys.Length;
+
+                    // --- Output ---
+                    GLTFAccessor accessorOutput = null;
+                    switch (gltfTarget.path)
+                    {
+                        case "translation":
+                            accessorOutput = GLTFBufferService.Instance.CreateAccessor(
+                                gltf,
+                                GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
+                                "accessorAnimationPositions",
+                                GLTFAccessor.ComponentType.FLOAT,
+                                GLTFAccessor.TypeEnum.VEC3
+                            );
+                            break;
+                        case "rotation":
+                            accessorOutput = GLTFBufferService.Instance.CreateAccessor(
+                                gltf,
+                                GLTFBufferService.Instance.GetBufferViewAnimationFloatVec4(gltf, buffer),
+                                "accessorAnimationRotations",
+                                GLTFAccessor.ComponentType.FLOAT,
+                                GLTFAccessor.TypeEnum.VEC4
+                            );
+                            break;
+                        case "scale":
+                            accessorOutput = GLTFBufferService.Instance.CreateAccessor(
+                                gltf,
+                                GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
+                                "accessorAnimationScales",
+                                GLTFAccessor.ComponentType.FLOAT,
+                                GLTFAccessor.TypeEnum.VEC3
+                            );
+                            break;
+                    }
+                    // Populate accessor
+                    foreach (var babylonAnimationKey in babylonAnimation.keys)
+                    {
+                        var outputValues = babylonAnimationKey.values;
+                        // Store values as bytes
+                        foreach (var outputValue in outputValues)
+                        {
+                            accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
+                        }
+                    };
+                    accessorOutput.count = babylonAnimation.keys.Length;
+
+                    // Animation sampler
+                    var gltfAnimationSampler = new GLTFAnimationSampler
+                    {
+                        input = accessorInput.index,
+                        output = accessorOutput.index
+                    };
+                    gltfAnimationSampler.index = samplerList.Count;
+                    samplerList.Add(gltfAnimationSampler);
+
+                    // Channel
+                    var gltfChannel = new GLTFChannel
+                    {
+                        sampler = gltfAnimationSampler.index,
+                        target = gltfTarget
+                    };
+                    channelList.Add(gltfChannel);
+                }
+
+                // Do not export empty arrays
+                if (channelList.Count > 0)
+                {
+                    // Animation
+                    var gltfAnimation = new GLTFAnimation
+                    {
+                        channels = channelList.ToArray(),
+                        samplers = samplerList.ToArray()
+                    };
+                    gltf.AnimationsList.Add(gltfAnimation);
+                    return gltfAnimation;
+                }
+                else
+                {
+                    return null;
+                }
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        private string _getTargetPath(string babylonProperty)
+        {
+            switch (babylonProperty)
+            {
+                case "position":
+                    return "translation";
+                case "rotationQuaternion":
+                    return "rotation";
+                case "scaling":
+                    return "scale";
+                default:
+                    return null;
+            }
+        }
+    }
+}

+ 102 - 317
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Mesh.cs

@@ -3,7 +3,6 @@ using BabylonExport.Entities;
 using GLTFExport.Entities;
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 
 namespace Max2Babylon
@@ -70,14 +69,6 @@ namespace Max2Babylon
             // Retreive indices from babylon mesh
             List<ushort> babylonIndices = new List<ushort>();
             babylonIndices = babylonMesh.indices.ToList().ConvertAll(new Converter<int, ushort>(n => (ushort)n));
-            // For triangle primitives in gltf, the front face has a counter-clockwise (CCW) winding order
-            // Swap face side
-            //for (int i = 0; i < babylonIndices.Count; i += 3)
-            //{
-            //    var tmp = babylonIndices[i];
-            //    babylonIndices[i] = babylonIndices[i + 2];
-            //    babylonIndices[i + 2] = tmp;
-            //}
 
 
             // --------------------------
@@ -91,107 +82,12 @@ namespace Max2Babylon
             gltf.MeshesList.Add(gltfMesh);
             gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
 
-            // Buffer
-            var buffer = gltf.buffer;
-            if (buffer == null)
-            {
-                buffer = new GLTFBuffer
-                {
-                    uri = gltf.OutputFile + ".bin"
-                };
-                buffer.index = gltf.BuffersList.Count;
-                gltf.BuffersList.Add(buffer);
-                gltf.buffer = buffer;
-            }
-
-            // BufferView - Scalar
-            var bufferViewScalar = gltf.bufferViewScalar;
-            if (bufferViewScalar == null)
-            {
-                bufferViewScalar = new GLTFBufferView
-                {
-                    name = "bufferViewScalar",
-                    buffer = buffer.index,
-                    Buffer = buffer
-                };
-                bufferViewScalar.index = gltf.BufferViewsList.Count;
-                gltf.BufferViewsList.Add(bufferViewScalar);
-                gltf.bufferViewScalar = bufferViewScalar;
-            }
-
-            // BufferView - Vector3
-            var bufferViewFloatVec3 = gltf.bufferViewFloatVec3;
-            if (bufferViewFloatVec3 == null)
-            {
-                bufferViewFloatVec3 = new GLTFBufferView
-                {
-                    name = "bufferViewFloatVec3",
-                    buffer = buffer.index,
-                    Buffer = buffer,
-                    byteOffset = 0,
-                    byteStride = 12 // Field only defined for buffer views that contain vertex attributes. A vertex needs 3 * 4 bytes
-                };
-                bufferViewFloatVec3.index = gltf.BufferViewsList.Count;
-                gltf.BufferViewsList.Add(bufferViewFloatVec3);
-                gltf.bufferViewFloatVec3 = bufferViewFloatVec3;
-            }
-
-            // BufferView - Vector4
-            GLTFBufferView bufferViewFloatVec4 = null;
-            if (hasColor)
-            {
-                bufferViewFloatVec4 = gltf.bufferViewFloatVec4;
-                if (bufferViewFloatVec4 == null)
-                {
-                    bufferViewFloatVec4 = new GLTFBufferView
-                    {
-                        name = "bufferViewFloatVec4",
-                        buffer = buffer.index,
-                        Buffer = buffer,
-                        byteOffset = 0,
-                        byteStride = 16 // Field only defined for buffer views that contain vertex attributes. A vertex needs 4 * 4 bytes
-                    };
-                    bufferViewFloatVec4.index = gltf.BufferViewsList.Count;
-                    gltf.BufferViewsList.Add(bufferViewFloatVec4);
-                    gltf.bufferViewFloatVec4 = bufferViewFloatVec4;
-                }
-            }
-
-            // BufferView - Vector2
-            GLTFBufferView bufferViewFloatVec2 = null;
-            if (hasUV || hasUV2)
-            {
-                bufferViewFloatVec2 = gltf.bufferViewFloatVec2;
-                if (bufferViewFloatVec2 == null)
-                {
-                    bufferViewFloatVec2 = new GLTFBufferView
-                    {
-                        name = "bufferViewFloatVec2",
-                        buffer = buffer.index,
-                        Buffer = buffer,
-                        byteStride = 8 // Field only defined for buffer views that contain vertex attributes. A vertex needs 2 * 4 bytes
-                    };
-                    bufferViewFloatVec2.index = gltf.BufferViewsList.Count;
-                    gltf.BufferViewsList.Add(bufferViewFloatVec2);
-                    gltf.bufferViewFloatVec2 = bufferViewFloatVec2;
-                }
-            }
-
             // --------------------------
             // ---- glTF primitives -----
             // --------------------------
 
             RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2);
             var meshPrimitives = new List<GLTFMeshPrimitive>();
-
-            // Global vertices are sorted per submesh
-            var globalVerticesSubMeshes = new List<List<GLTFGlobalVertex>>();
-
-            // In gltf, indices of each mesh primitive are 0-based (ie: min value is 0)
-            // Thus, the gltf indices list is a concatenation of sub lists all 0-based
-            // Example for 2 triangles, each being a submesh:
-            //      babylonIndices = {0,1,2, 3,4,5} gives as result gltfIndicies = {0,1,2, 0,1,2}
-            var gltfIndices = new List<ushort>();
             
             foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes)
             {
@@ -200,19 +96,20 @@ namespace Max2Babylon
                 // --------------------------
 
                 List<GLTFGlobalVertex> globalVerticesSubMesh = globalVertices.GetRange(babylonSubMesh.verticesStart, babylonSubMesh.verticesCount);
-                globalVerticesSubMeshes.Add(globalVerticesSubMesh);
 
-                List<ushort> _indices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
-                // Indices of this submesh / primitive are updated to be 0-based
-                var minIndiceValue = _indices.Min(); // Should be equal to babylonSubMesh.indexStart
-                for (int indexIndice = 0; indexIndice < _indices.Count; indexIndice++)
+                List<ushort> gltfIndices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
+                // In gltf, indices of each mesh primitive are 0-based (ie: min value is 0)
+                // Thus, the gltf indices list is a concatenation of sub lists all 0-based
+                // Example for 2 triangles, each being a submesh:
+                //      babylonIndices = {0,1,2, 3,4,5} gives as result gltfIndicies = {0,1,2, 0,1,2}
+                var minIndiceValue = gltfIndices.Min(); // Should be equal to babylonSubMesh.indexStart
+                for (int indexIndice = 0; indexIndice < gltfIndices.Count; indexIndice++)
                 {
-                    _indices[indexIndice] -= minIndiceValue;
+                    gltfIndices[indexIndice] -= minIndiceValue;
                 }
-                gltfIndices.AddRange(_indices);
 
                 // --------------------------
-                // -- Init glTF primitive ---
+                // ----- Mesh primitive -----
                 // --------------------------
 
                 // MeshPrimitive
@@ -222,105 +119,6 @@ namespace Max2Babylon
                 };
                 meshPrimitives.Add(meshPrimitive);
 
-                // Accessor - Indices
-                var accessorIndices = new GLTFAccessor
-                {
-                    name = "accessorIndices",
-                    bufferView = bufferViewScalar.index,
-                    BufferView = bufferViewScalar,
-                    componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT,
-                    type = GLTFAccessor.TypeEnum.SCALAR.ToString()
-                };
-                accessorIndices.index = gltf.AccessorsList.Count;
-                gltf.AccessorsList.Add(accessorIndices);
-                meshPrimitive.indices = accessorIndices.index;
-
-                // Accessor - Positions
-                var accessorPositions = new GLTFAccessor
-                {
-                    name = "accessorPositions",
-                    bufferView = bufferViewFloatVec3.index,
-                    BufferView = bufferViewFloatVec3,
-                    componentType = GLTFAccessor.ComponentType.FLOAT,
-                    type = GLTFAccessor.TypeEnum.VEC3.ToString(),
-                    min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue },
-                    max = new float[] { float.MinValue, float.MinValue, float.MinValue }
-                };
-                accessorPositions.index = gltf.AccessorsList.Count;
-                gltf.AccessorsList.Add(accessorPositions);
-                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
-
-                // Accessor - Normals
-                var accessorNormals = new GLTFAccessor
-                {
-                    name = "accessorNormals",
-                    bufferView = bufferViewFloatVec3.index,
-                    BufferView = bufferViewFloatVec3,
-                    componentType = GLTFAccessor.ComponentType.FLOAT,
-                    type = GLTFAccessor.TypeEnum.VEC3.ToString()
-                };
-                accessorNormals.index = gltf.AccessorsList.Count;
-                gltf.AccessorsList.Add(accessorNormals);
-                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);
-
-                // Accessor - Colors
-                GLTFAccessor accessorColors = null;
-                if (hasColor)
-                {
-                    accessorColors = new GLTFAccessor
-                    {
-                        name = "accessorColors",
-                        bufferView = bufferViewFloatVec4.index,
-                        BufferView = bufferViewFloatVec4,
-                        componentType = GLTFAccessor.ComponentType.FLOAT,
-                        type = GLTFAccessor.TypeEnum.VEC4.ToString()
-                    };
-                    accessorColors.index = gltf.AccessorsList.Count;
-                    gltf.AccessorsList.Add(accessorColors);
-                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
-                }
-
-                // Accessor - UV
-                GLTFAccessor accessorUVs = null;
-                if (hasUV)
-                {
-                    accessorUVs = new GLTFAccessor
-                    {
-                        name = "accessorUVs",
-                        bufferView = bufferViewFloatVec2.index,
-                        BufferView = bufferViewFloatVec2,
-                        componentType = GLTFAccessor.ComponentType.FLOAT,
-                        type = GLTFAccessor.TypeEnum.VEC2.ToString()
-                    };
-                    accessorUVs.index = gltf.AccessorsList.Count;
-                    gltf.AccessorsList.Add(accessorUVs);
-                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
-                }
-
-                // Accessor - UV2
-                GLTFAccessor accessorUV2s = null;
-                if (hasUV2)
-                {
-                    accessorUV2s = new GLTFAccessor
-                    {
-                        name = "accessorUV2s",
-                        bufferView = bufferViewFloatVec2.index,
-                        BufferView = bufferViewFloatVec2,
-                        componentType = GLTFAccessor.ComponentType.FLOAT,
-                        type = GLTFAccessor.TypeEnum.VEC2.ToString()
-                    };
-                    accessorUV2s.index = gltf.AccessorsList.Count;
-                    gltf.AccessorsList.Add(accessorUV2s);
-                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
-                }
-
-                
-                // --------------------------
-                // - Update glTF primitive --
-                // --------------------------
-
-                RaiseMessage("GLTFExporter.Mesh | Mesh as glTF", 3);
-
                 // Material
                 if (babylonMesh.materialId != null)
                 {
@@ -351,119 +149,117 @@ namespace Max2Babylon
                     meshPrimitive.mode = GLTFMeshPrimitive.FillMode.TRIANGLES;
                 }
 
-                // Update min and max vertex position for each component (X, Y, Z)
+                // --------------------------
+                // ------- Accessors --------
+                // --------------------------
+
+                // Buffer
+                var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
+
+                // --- Indices ---
+                var accessorIndices = GLTFBufferService.Instance.CreateAccessor(
+                    gltf,
+                    GLTFBufferService.Instance.GetBufferViewScalar(gltf, buffer),
+                    "accessorIndices",
+                    GLTFAccessor.ComponentType.UNSIGNED_SHORT,
+                    GLTFAccessor.TypeEnum.SCALAR
+                );
+                meshPrimitive.indices = accessorIndices.index;
+                // Populate accessor
+                gltfIndices.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n)));
+                accessorIndices.count = gltfIndices.Count;
+                
+                // --- Positions ---
+                var accessorPositions = GLTFBufferService.Instance.CreateAccessor(
+                    gltf,
+                    GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
+                    "accessorPositions",
+                    GLTFAccessor.ComponentType.FLOAT,
+                    GLTFAccessor.TypeEnum.VEC3
+                );
+                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
+                // Populate accessor
+                accessorPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
+                accessorPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
                 globalVerticesSubMesh.ForEach((globalVertex) =>
                 {
-                    var positionArray = new float[] { globalVertex.Position.X, globalVertex.Position.Y, globalVertex.Position.Z };
-                    for (int indexComponent = 0; indexComponent < positionArray.Length; indexComponent++)
+                    var positions = new float[] { globalVertex.Position.X, globalVertex.Position.Y, globalVertex.Position.Z };
+                    // Store values as bytes
+                    foreach (var position in positions)
                     {
-                        if (positionArray[indexComponent] < accessorPositions.min[indexComponent])
-                        {
-                            accessorPositions.min[indexComponent] = positionArray[indexComponent];
-                        }
-                        if (positionArray[indexComponent] > accessorPositions.max[indexComponent])
-                        {
-                            accessorPositions.max[indexComponent] = positionArray[indexComponent];
-                        }
+                        accessorPositions.bytesList.AddRange(BitConverter.GetBytes(position));
                     }
+                    // Update min and max values
+                    GLTFBufferService.UpdateMinMaxAccessor(accessorPositions, positions);
                 });
-
-                // Update byte length and count of accessors, bufferViews and buffers
-                // Scalar
-                AddElementsToAccessor(accessorIndices, _indices.Count);
-                // Ensure the byteoffset is a multiple of 4
-                // Indices accessor element size if 2
-                // So the count needs to be even
-                if (gltfIndices.Count % 2 != 0)
-                {
-                    gltfIndices.Add(0);
-                    bufferViewScalar.byteLength += 2;
-                    buffer.byteLength += 2;
-                }
-                // Vector3
-                AddElementsToAccessor(accessorPositions, globalVerticesSubMesh.Count);
-                AddElementsToAccessor(accessorNormals, globalVerticesSubMesh.Count);
-                // Vector4
-                if (hasColor)
-                {
-                    AddElementsToAccessor(accessorColors, globalVerticesSubMesh.Count);
-                }
-                // Vector2
-                if (hasUV)
-                {
-                    AddElementsToAccessor(accessorUVs, globalVerticesSubMesh.Count);
-                }
-                if (hasUV2)
-                {
-                    AddElementsToAccessor(accessorUV2s, globalVerticesSubMesh.Count);
-                }
-            }
-            gltfMesh.primitives = meshPrimitives.ToArray();
-            
-            // Update byte offset of bufferViews
-            GLTFBufferView lastBufferView = null;
-            gltf.BufferViewsList.FindAll(bufferView => bufferView.buffer == buffer.index).ForEach(bufferView =>
-            {
-                if (lastBufferView != null)
-                {
-                    bufferView.byteOffset = lastBufferView.byteOffset + lastBufferView.byteLength;
-                }
-                lastBufferView = bufferView;
-            });
-
-
-            // --------------------------
-            // --------- Saving ---------
-            // --------------------------
-
-            RaiseMessage("GLTFExporter.Mesh | saving", 2);
-
-            // BufferView - Scalar
-            gltfIndices.ForEach(n => bufferViewScalar.bytesList.AddRange(BitConverter.GetBytes(n)));
-
-            // BufferView - Vector3
-            globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
-            {
-                List<float> vertices = globalVerticesSubMesh.SelectMany(v => new[] { v.Position.X, v.Position.Y, v.Position.Z }).ToList();
-                vertices.ForEach(n => bufferViewFloatVec3.bytesList.AddRange(BitConverter.GetBytes(n)));
-
+                accessorPositions.count = globalVerticesSubMesh.Count;
+                
+                // --- Normals ---
+                var accessorNormals = GLTFBufferService.Instance.CreateAccessor(
+                    gltf,
+                    GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
+                    "accessorNormals",
+                    GLTFAccessor.ComponentType.FLOAT,
+                    GLTFAccessor.TypeEnum.VEC3
+                );
+                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);
+                // Populate accessor
                 List<float> normals = globalVerticesSubMesh.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToList();
-                normals.ForEach(n => bufferViewFloatVec3.bytesList.AddRange(BitConverter.GetBytes(n)));
-            });
-
-            // BufferView - Vector4
-            globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
-            {
+                normals.ForEach(n => accessorNormals.bytesList.AddRange(BitConverter.GetBytes(n)));
+                accessorNormals.count = globalVerticesSubMesh.Count;
+                
+                // --- Colors ---
                 if (hasColor)
                 {
+                    var accessorColors = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
+                        "accessorColors",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.VEC4
+                    );
+                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
+                    // Populate accessor
                     List<float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
-                    colors.ForEach(n => bufferViewFloatVec4.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    colors.ForEach(n => accessorColors.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    accessorColors.count = globalVerticesSubMesh.Count;
                 }
-            });
-
-            // BufferView - Vector2
-            globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
-            {
+                
+                // --- UV ---
                 if (hasUV)
                 {
+                    var accessorUVs = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
+                        "accessorUVs",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.VEC2
+                    );
+                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
+                    // Populate accessor
                     List<float> uvs = globalVerticesSubMesh.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList();
-                    uvs.ForEach(n => bufferViewFloatVec2.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    uvs.ForEach(n => accessorUVs.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    accessorUVs.count = globalVerticesSubMesh.Count;
                 }
-
+                
+                // --- UV2 ---
                 if (hasUV2)
                 {
+                    var accessorUV2s = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
+                        "accessorUV2s",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.VEC2
+                    );
+                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
+                    // Populate accessor
                     List<float> uvs2 = globalVerticesSubMesh.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList();
-                    uvs2.ForEach(n => bufferViewFloatVec2.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    uvs2.ForEach(n => accessorUV2s.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    accessorUV2s.count = globalVerticesSubMesh.Count;
                 }
-            });
-
-            //// Write data to binary file
-            //string outputBinaryFile = Path.Combine(gltf.OutputPath, gltfMesh.name + ".bin");
-            //RaiseMessage("GLTFExporter.Mesh | Saving " + outputBinaryFile, 2);
-            //using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
-            //{
-            //    bytesList.ForEach(b => writer.Write(b));
-            //}
+            }
+            gltfMesh.primitives = meshPrimitives.ToArray();
 
             return gltfMesh;
         }
@@ -485,16 +281,5 @@ namespace Max2Babylon
             var startIndex = index * 4;
             return Loader.Global.Point4.Create(array[startIndex], array[startIndex + 1], array[startIndex + 2], array[startIndex + 3]);
         }
-
-        private void AddElementsToAccessor(GLTFAccessor accessor, int count)
-        {
-            GLTFBufferView bufferView = accessor.BufferView;
-            GLTFBuffer buffer = bufferView.Buffer;
-
-            accessor.byteOffset = bufferView.byteLength;
-            accessor.count += count;
-            bufferView.byteLength += accessor.getByteLength();
-            buffer.byteLength += accessor.getByteLength();
-        }
     }
 }

+ 27 - 7
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.cs

@@ -96,27 +96,47 @@ namespace Max2Babylon
             };
             RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);
 
+            // Prepare buffers
+            gltf.BuffersList.ForEach(buffer =>
+            {
+                buffer.BufferViews.ForEach(bufferView =>
+                {
+                    bufferView.Accessors.ForEach(accessor =>
+                    {
+                        // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements
+                        accessor.bytesList = new List<byte>(padChunk(accessor.bytesList.ToArray(), 4, 0x00));
+
+                        // Update byte properties
+                        accessor.byteOffset = bufferView.byteLength;
+                        bufferView.byteLength += accessor.bytesList.Count;
+                        // Merge bytes
+                        bufferView.bytesList.AddRange(accessor.bytesList);
+                    });
+                    // Update byte properties
+                    bufferView.byteOffset = buffer.byteLength;
+                    buffer.byteLength += bufferView.bytesList.Count;
+                    // Merge bytes
+                    buffer.bytesList.AddRange(bufferView.bytesList);
+                });
+            });
+
             // Cast lists to arrays
             gltf.Prepare();
 
             // Output
             RaiseMessage("GLTFExporter | Saving to output file");
             
+            // Write .gltf file
             string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
             File.WriteAllText(outputGltfFile, gltfToJson(gltf));
 
-            // Write data to binary file
+            // Write .bin file
             string outputBinaryFile = Path.ChangeExtension(outputFile, "bin");
             using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
             {
                 gltf.BuffersList.ForEach(buffer =>
                 {
-                    buffer.bytesList = new List<byte>();
-                    gltf.BufferViewsList.FindAll(bufferView => bufferView.buffer == buffer.index).ForEach(bufferView =>
-                    {
-                        bufferView.bytesList.ForEach(b => writer.Write(b));
-                        buffer.bytesList.AddRange(bufferView.bytesList);
-                    });
+                    buffer.bytesList.ForEach(b => writer.Write(b));
                 });
             }
 

+ 158 - 0
Exporters/3ds Max/Max2Babylon/Exporter/GLTFBufferService.cs

@@ -0,0 +1,158 @@
+using GLTFExport.Entities;
+
+namespace Max2Babylon
+{
+    public class GLTFBufferService
+    {
+        private static GLTFBufferService _instance;
+
+        public static GLTFBufferService Instance
+        {
+            get
+            {
+                if (_instance == null)
+                {
+                    _instance = new GLTFBufferService();
+                }
+                return _instance;
+            }
+        }
+
+        public GLTFBuffer GetBuffer(GLTF gltf)
+        {
+            var buffer = gltf.buffer;
+            if (buffer == null)
+            {
+                buffer = new GLTFBuffer
+                {
+                    uri = gltf.OutputFile + ".bin"
+                };
+                buffer.index = gltf.BuffersList.Count;
+                gltf.BuffersList.Add(buffer);
+                gltf.buffer = buffer;
+            }
+            return buffer;
+        }
+
+        public GLTFBufferView GetBufferViewScalar(GLTF gltf, GLTFBuffer buffer)
+        {
+            if (gltf.bufferViewScalar == null)
+            {
+                gltf.bufferViewScalar = CreateBufferView(gltf, buffer, "bufferViewScalar");
+            }
+            return gltf.bufferViewScalar;
+        }
+
+        public GLTFBufferView GetBufferViewFloatVec2(GLTF gltf, GLTFBuffer buffer)
+        {
+            if (gltf.bufferViewFloatVec2 == null)
+            {
+                var bufferView = CreateBufferView(gltf, buffer, "bufferViewFloatVec2");
+                bufferView.byteStride = 8;
+                gltf.bufferViewFloatVec2 = bufferView;
+            }
+            return gltf.bufferViewFloatVec2;
+        }
+
+        public GLTFBufferView GetBufferViewFloatVec3(GLTF gltf, GLTFBuffer buffer)
+        {
+            if (gltf.bufferViewFloatVec3 == null)
+            {
+                var bufferView = CreateBufferView(gltf, buffer, "bufferViewFloatVec3");
+                bufferView.byteStride = 12;
+                gltf.bufferViewFloatVec3 = bufferView;
+            }
+            return gltf.bufferViewFloatVec3;
+        }
+
+        public GLTFBufferView GetBufferViewFloatVec4(GLTF gltf, GLTFBuffer buffer)
+        {
+            if (gltf.bufferViewFloatVec4 == null)
+            {
+                var bufferView = CreateBufferView(gltf, buffer, "bufferViewFloatVec4");
+                bufferView.byteStride = 16;
+                gltf.bufferViewFloatVec4 = bufferView;
+            }
+            return gltf.bufferViewFloatVec4;
+        }
+
+        public GLTFBufferView GetBufferViewAnimationFloatScalar(GLTF gltf, GLTFBuffer buffer)
+        {
+            if (gltf.bufferViewAnimationFloatScalar == null)
+            {
+                gltf.bufferViewAnimationFloatScalar = CreateBufferView(gltf, buffer, "bufferViewAnimationFloatScalar");
+            }
+            return gltf.bufferViewAnimationFloatScalar;
+        }
+
+        public GLTFBufferView GetBufferViewAnimationFloatVec3(GLTF gltf, GLTFBuffer buffer)
+        {
+            if (gltf.bufferViewAnimationFloatVec3 == null)
+            {
+                var bufferView = CreateBufferView(gltf, buffer, "bufferViewAnimationFloatVec3");
+                gltf.bufferViewAnimationFloatVec3 = bufferView;
+            }
+            return gltf.bufferViewAnimationFloatVec3;
+        }
+
+        public GLTFBufferView GetBufferViewAnimationFloatVec4(GLTF gltf, GLTFBuffer buffer)
+        {
+            if (gltf.bufferViewAnimationFloatVec4 == null)
+            {
+                var bufferView = CreateBufferView(gltf, buffer, "bufferViewAnimationFloatVec4");
+                gltf.bufferViewAnimationFloatVec4 = bufferView;
+            }
+            return gltf.bufferViewAnimationFloatVec4;
+        }
+
+        private GLTFBufferView CreateBufferView(GLTF gltf, GLTFBuffer buffer, string name)
+        {
+            var bufferView = new GLTFBufferView
+            {
+                name = name,
+                buffer = buffer.index,
+                Buffer = buffer
+            };
+            bufferView.index = gltf.BufferViewsList.Count;
+            gltf.BufferViewsList.Add(bufferView);
+            buffer.BufferViews.Add(bufferView);
+            return bufferView;
+        }
+
+        public GLTFAccessor CreateAccessor(GLTF gltf, GLTFBufferView bufferView, string name, GLTFAccessor.ComponentType componentType, GLTFAccessor.TypeEnum type)
+        {
+            var accessor = new GLTFAccessor
+            {
+                name = name,
+                bufferView = bufferView.index,
+                BufferView = bufferView,
+                componentType = componentType,
+                type = type.ToString()
+            };
+            accessor.index = gltf.AccessorsList.Count;
+            gltf.AccessorsList.Add(accessor);
+            bufferView.Accessors.Add(accessor);
+            return accessor;
+        }
+
+        public static void UpdateMinMaxAccessor(GLTFAccessor accessor, float[] values)
+        {
+            for (int indexComponent = 0; indexComponent < values.Length; indexComponent++)
+            {
+                UpdateMinMaxAccessor(accessor, values[indexComponent], indexComponent);
+            }
+        }
+
+        public static void UpdateMinMaxAccessor(GLTFAccessor accessor, float value, int indexComponent = 0)
+        {
+            if (value < accessor.min[indexComponent])
+            {
+                accessor.min[indexComponent] = value;
+            }
+            if (value > accessor.max[indexComponent])
+            {
+                accessor.max[indexComponent] = value;
+            }
+        }
+    }
+}