Browse Source

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David Catuhe 7 years ago
parent
commit
b3a935267e
32 changed files with 1415 additions and 436 deletions
  1. 11 2
      Exporters/3ds Max/BabylonExport.Entities/BabylonAnimationKey.cs
  2. 2 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonExport.Entities.csproj
  3. 3 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonMesh.cs
  4. 23 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonMorphTarget.cs
  5. 26 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonMorphTargetManager.cs
  6. 9 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonScene.cs
  7. 12 0
      Exporters/3ds Max/GltfExport.Entities/GLTF.cs
  8. 3 0
      Exporters/3ds Max/GltfExport.Entities/GLTFAccessor.cs
  9. 22 0
      Exporters/3ds Max/GltfExport.Entities/GLTFAnimation.cs
  10. 49 0
      Exporters/3ds Max/GltfExport.Entities/GLTFAnimationSampler.cs
  11. 2 1
      Exporters/3ds Max/GltfExport.Entities/GLTFBuffer.cs
  12. 2 1
      Exporters/3ds Max/GltfExport.Entities/GLTFBufferView.cs
  13. 21 0
      Exporters/3ds Max/GltfExport.Entities/GLTFChannel.cs
  14. 20 0
      Exporters/3ds Max/GltfExport.Entities/GLTFChannelTarget.cs
  15. 5 0
      Exporters/3ds Max/GltfExport.Entities/GLTFExport.Entities.csproj
  16. 2 2
      Exporters/3ds Max/GltfExport.Entities/GLTFMeshPrimitive.cs
  17. 16 0
      Exporters/3ds Max/GltfExport.Entities/GLTFMorphTarget.cs
  18. BIN
      Exporters/3ds Max/Max2Babylon-0.17.0.zip
  19. 6 0
      Exporters/3ds Max/Max2Babylon/2015/Max2Babylon2015.csproj
  20. 6 0
      Exporters/3ds Max/Max2Babylon/2017/Max2Babylon2017.csproj
  21. 102 30
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Animation.cs
  22. 4 1
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.AbstractMesh.cs
  23. 416 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Animation.cs
  24. 2 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Camera.cs
  25. 3 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Light.cs
  26. 221 305
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Mesh.cs
  27. 29 9
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.cs
  28. 203 84
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Mesh.cs
  29. 7 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.cs
  30. 158 0
      Exporters/3ds Max/Max2Babylon/Exporter/GLTFBufferService.cs
  31. 18 1
      Exporters/3ds Max/Max2Babylon/Tools/Tools.cs
  32. 12 0
      src/Morph/babylon.morphTarget.ts

+ 11 - 2
Exporters/3ds Max/BabylonExport.Entities/BabylonAnimationKey.cs

@@ -1,14 +1,23 @@
-using System.Runtime.Serialization;
+using System;
+using System.Runtime.Serialization;
 
 namespace BabylonExport.Entities
 {
     [DataContract]
-    public class BabylonAnimationKey
+    public class BabylonAnimationKey : IComparable<BabylonAnimationKey>
     {
         [DataMember]
         public int frame { get; set; }
 
         [DataMember]
         public float[] values { get; set; }
+        
+        public int CompareTo(BabylonAnimationKey other)
+        {
+            if (other == null)
+                return 1;
+            else
+                return this.frame.CompareTo(other.frame);
+        }
     }
 }

+ 2 - 0
Exporters/3ds Max/BabylonExport.Entities/BabylonExport.Entities.csproj

@@ -56,6 +56,8 @@
   <ItemGroup>
     <Compile Include="BabylonActions.cs" />
     <Compile Include="BabylonAnimation.cs" />
+    <Compile Include="BabylonMorphTarget.cs" />
+    <Compile Include="BabylonMorphTargetManager.cs" />
     <Compile Include="BabylonPBRMetallicRoughnessMaterial.cs" />
     <Compile Include="BabylonAnimationKey.cs" />
     <Compile Include="BabylonBone.cs" />

+ 3 - 0
Exporters/3ds Max/BabylonExport.Entities/BabylonMesh.cs

@@ -110,6 +110,9 @@ namespace BabylonExport.Entities
         [DataMember]
         public string tags { get; set; }
 
+        [DataMember(EmitDefaultValue = false)]
+        public int? morphTargetManagerId { get; set; }
+
         public bool isDummy = false;
 
         public BabylonMesh()

+ 23 - 0
Exporters/3ds Max/BabylonExport.Entities/BabylonMorphTarget.cs

@@ -0,0 +1,23 @@
+using System.Runtime.Serialization;
+
+namespace BabylonExport.Entities
+{
+    [DataContract]
+    public class BabylonMorphTarget
+    {
+        [DataMember(EmitDefaultValue = false)]
+        public string name { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public float influence { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public float[] positions { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public float[] normals { get; set; }
+
+        [DataMember(EmitDefaultValue = false)]
+        public BabylonAnimation[] animations { get; set; }
+    }
+}

+ 26 - 0
Exporters/3ds Max/BabylonExport.Entities/BabylonMorphTargetManager.cs

@@ -0,0 +1,26 @@
+using System.Runtime.Serialization;
+
+namespace BabylonExport.Entities
+{
+    [DataContract]
+    public class BabylonMorphTargetManager
+    {
+        private static int NB_BABYLON_MORPH_TARGET_MANAGER;
+
+        [DataMember(IsRequired = true)]
+        public int id { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public BabylonMorphTarget[] targets { get; set; }
+
+        public static void Reset()
+        {
+            NB_BABYLON_MORPH_TARGET_MANAGER = 0;
+        }
+
+        public BabylonMorphTargetManager()
+        {
+            id = NB_BABYLON_MORPH_TARGET_MANAGER++;
+        }
+    }
+}

+ 9 - 0
Exporters/3ds Max/BabylonExport.Entities/BabylonScene.cs

@@ -89,6 +89,9 @@ namespace BabylonExport.Entities
         [DataMember]
         public bool workerCollisions { get; set; }
 
+        [DataMember]
+        public BabylonMorphTargetManager[] morphTargetManagers { get; set; }
+
         public BabylonVector3 MaxVector { get; set; }
         public BabylonVector3 MinVector { get; set; }
 
@@ -102,6 +105,7 @@ namespace BabylonExport.Entities
         public List<BabylonMultiMaterial> MultiMaterialsList { get; private set; }
         public List<BabylonShadowGenerator> ShadowGeneratorsList { get; private set; }
         public List<BabylonSkeleton> SkeletonsList { get; private set; }
+        public List<BabylonMorphTargetManager> MorphTargetManagersList { get; private set; }
 
         readonly List<string> exportedTextures = new List<string>();
 
@@ -117,6 +121,7 @@ namespace BabylonExport.Entities
             ShadowGeneratorsList = new List<BabylonShadowGenerator>();
             SkeletonsList = new List<BabylonSkeleton>();
             SoundsList = new List<BabylonSound>();
+            MorphTargetManagersList = new List<BabylonMorphTargetManager>();
 
             // Default values
             autoClear = true;
@@ -137,6 +142,10 @@ namespace BabylonExport.Entities
             multiMaterials = MultiMaterialsList.ToArray();
             shadowGenerators = ShadowGeneratorsList.ToArray();
             skeletons = SkeletonsList.ToArray();
+            if (MorphTargetManagersList.Count > 0)
+            {
+                morphTargetManagers = MorphTargetManagersList.ToArray();
+            }
 
             if (CamerasList.Count == 0 && generateDefaultCamera)
             {

+ 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; }
+    }
+}

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

@@ -42,11 +42,16 @@
   <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" />
     <Compile Include="GLTFCameraOrthographic.cs" />
     <Compile Include="GLTFCamera.cs" />
+    <Compile Include="GLTFMorphTarget.cs" />
     <Compile Include="GLTFSampler.cs" />
     <Compile Include="GLTFIndexedChildRootProperty.cs" />
     <Compile Include="GLTFImage.cs" />

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

@@ -41,7 +41,7 @@ namespace GLTFExport.Entities
         [DataMember(EmitDefaultValue = false)]
         public int? material { get; set; }
 
-        //[DataMember]
-        //public Dictionary<string, int>[] targets { get; set; }
+        [DataMember(EmitDefaultValue = false)]
+        public GLTFMorphTarget[] targets { get; set; }
     }
 }

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

@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace GLTFExport.Entities
+{
+    [DataContract]
+    public class GLTFMorphTarget : Dictionary<string, int>
+    {
+        public enum Attribute
+        {
+            POSITION,
+            NORMAL,
+            TANGENT
+        }
+    }
+}

BIN
Exporters/3ds Max/Max2Babylon-0.17.0.zip


+ 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>

+ 102 - 30
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Animation.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using Autodesk.Max;
 using BabylonExport.Entities;
+using System.Runtime.InteropServices;
 
 namespace Max2Babylon
 {
@@ -9,6 +10,101 @@ namespace Max2Babylon
     {
         const int Ticks = 160;
 
+        private static bool ExportBabylonKeys(List<BabylonAnimationKey> keys, string property, List<BabylonAnimation> animations, BabylonAnimation.DataType dataType, BabylonAnimation.LoopBehavior loopBehavior)
+        {
+            if (keys.Count == 0)
+            {
+                return false;
+            }
+
+            var end = Loader.Core.AnimRange.End;
+            if (keys[keys.Count - 1].frame != end / Ticks)
+            {
+                keys.Add(new BabylonAnimationKey()
+                {
+                    frame = end / Ticks,
+                    values = keys[keys.Count - 1].values
+                });
+            }
+
+            var babylonAnimation = new BabylonAnimation
+            {
+                dataType = (int)dataType,
+                name = property + " animation",
+                keys = keys.ToArray(),
+                framePerSecond = Loader.Global.FrameRate,
+                loopBehavior = (int)loopBehavior,
+                property = property
+            };
+
+            animations.Add(babylonAnimation);
+
+            return true;
+        }
+
+        // -----------------------
+        // -- From GameControl ---
+        // -----------------------
+
+        private bool ExportFloatGameController(IIGameControl control, string property, List<BabylonAnimation> animations)
+        {
+            return ExportGameController(control, property, animations, IGameControlType.Float, BabylonAnimation.DataType.Float, gameKey => new float[] { gameKey.SampleKey.Fval / 100.0f });
+        }
+
+        private bool ExportGameController(IIGameControl control, string property, List<BabylonAnimation> animations, IGameControlType type, BabylonAnimation.DataType dataType, Func<IIGameKey, float[]> extractValueFunc)
+        {
+            var keys = ExportBabylonKeysFromGameController(control, type, extractValueFunc);
+
+            if (keys == null)
+            {
+                return false;
+            }
+
+            var loopBehavior = BabylonAnimation.LoopBehavior.Cycle;
+            return ExportBabylonKeys(keys, property, animations, dataType, loopBehavior);
+        }
+
+        private List<BabylonAnimationKey> ExportBabylonKeysFromGameController(IIGameControl control, IGameControlType type, Func<IIGameKey, float[]> extractValueFunc)
+        {
+            if (control == null)
+            {
+                return null;
+            }
+
+            ITab<IIGameKey> gameKeyTab = GlobalInterface.Instance.Tab.Create<IIGameKey>();
+            control.GetQuickSampledKeys(gameKeyTab, type);
+
+            if (gameKeyTab == null)
+            {
+                return null;
+            }
+
+            var keys = new List<BabylonAnimationKey>();
+            for (int indexKey = 0; indexKey < gameKeyTab.Count; indexKey++)
+            {
+#if MAX2017
+                var indexer = indexKey;
+#else
+                    var indexer = new IntPtr(indexKey);
+                    Marshal.FreeHGlobal(indexer);
+#endif
+                var gameKey = gameKeyTab[indexer];
+
+                var key = new BabylonAnimationKey()
+                {
+                    frame = gameKey.T / Ticks,
+                    values = extractValueFunc(gameKey)
+                };
+                keys.Add(key);
+            }
+
+            return keys;
+        }
+
+        // -----------------------
+        // ---- From Control -----
+        // -----------------------
+
         private static BabylonAnimationKey GenerateFloatFunc(int index, IIKeyControl keyControl)
         {
             var key = Loader.Global.ILinFloatKey.Create();
@@ -119,9 +215,7 @@ namespace Max2Babylon
                 return false;
             }
 
-            var keys = new List<BabylonAnimationKey>();
             BabylonAnimation.LoopBehavior loopBehavior;
-
             switch (control.GetORT(2))
             {
                 case 2:
@@ -132,41 +226,19 @@ namespace Max2Babylon
                     break;
             }
 
+            var keys = new List<BabylonAnimationKey>();
             for (var index = 0; index < keyControl.NumKeys; index++)
             {
                 keys.Add(generateFunc(index, keyControl));
             }
 
-            if (keys.Count == 0)
-            {
-                return false;
-            }
-
-            var end = Loader.Core.AnimRange.End;
-            if (keys[keys.Count - 1].frame != end / Ticks)
-            {
-                keys.Add(new BabylonAnimationKey()
-                {
-                    frame = end / Ticks,
-                    values = keys[keys.Count - 1].values
-                });
-            }
-
-            var babylonAnimation = new BabylonAnimation
-            {
-                dataType = (int)dataType,
-                name = property + " animation",
-                keys = keys.ToArray(),
-                framePerSecond = Loader.Global.FrameRate,
-                loopBehavior = (int)loopBehavior,
-                property = property
-            };
-
-            animations.Add(babylonAnimation);
-
-            return true;
+            return ExportBabylonKeys(keys, property, animations, dataType, loopBehavior);
         }
 
+        // -----------------------
+        // ---- From ext func ----
+        // -----------------------
+
         private static void ExportColor3Animation(string property, List<BabylonAnimation> animations,
             Func<int, float[]> extractValueFunc)
         {

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

@@ -5,7 +5,7 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private GLTFNode ExportAbstractMesh(BabylonAbstractMesh babylonAbstractMesh, GLTF gltf, GLTFNode gltfParentNode)
+        private GLTFNode ExportAbstractMesh(BabylonAbstractMesh babylonAbstractMesh, GLTF gltf, GLTFNode gltfParentNode, BabylonScene babylonScene)
         {
             RaiseMessage("GLTFExporter.AbstractMesh | Export abstract mesh named: " + babylonAbstractMesh.name, 1);
 
@@ -55,6 +55,9 @@ namespace Max2Babylon
                 gltfNode.mesh = gltfMesh.index;
             }
 
+            // Animations
+            ExportNodeAnimation(babylonAbstractMesh, gltf, gltfNode, babylonScene);
+
             return gltfNode;
         }
     }

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

@@ -0,0 +1,416 @@
+using BabylonExport.Entities;
+using GLTFExport.Entities;
+using System;
+using System.Collections.Generic;
+
+namespace Max2Babylon
+{
+    partial class BabylonExporter
+    {
+        private static float FPS_FACTOR = 60.0f; // TODO - Which FPS factor ?
+
+        private GLTFAnimation ExportNodeAnimation(BabylonNode babylonNode, GLTF gltf, GLTFNode gltfNode, BabylonScene babylonScene = null)
+        {
+            var channelList = new List<GLTFChannel>();
+            var samplerList = new List<GLTFAnimationSampler>();
+
+            if (babylonNode.animations != null && babylonNode.animations.Length > 0)
+            {
+                RaiseMessage("GLTFExporter.Animation | Export animation of node named: " + babylonNode.name, 2);
+
+                foreach (BabylonAnimation babylonAnimation in babylonNode.animations)
+                {
+                    // Target
+                    var gltfTarget = new GLTFChannelTarget
+                    {
+                        node = gltfNode.index
+                    };
+                    gltfTarget.path = _getTargetPath(babylonAnimation.property);
+                    if (gltfTarget.path == null)
+                    {
+                        // Unkown babylon animation property
+                        RaiseWarning("GLTFExporter.Animation | 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 / 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);
+                }
+            }
+
+            if (babylonNode.GetType() == typeof(BabylonMesh))
+            {
+                var babylonMesh = babylonNode as BabylonMesh;
+
+                // Morph targets
+                var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);
+                if (babylonMorphTargetManager != null)
+                {
+                    ExportMorphTargetWeightAnimation(babylonMorphTargetManager, gltf, gltfNode, channelList, samplerList);
+                }
+            }
+
+            // 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;
+            }
+        }
+
+        private bool ExportMorphTargetWeightAnimation(BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFNode gltfNode, List<GLTFChannel> channelList, List<GLTFAnimationSampler> samplerList)
+        {
+            if (!_isBabylonMorphTargetManagerAnimationValid(babylonMorphTargetManager))
+            {
+                return false;
+            }
+
+            RaiseMessage("GLTFExporter.Animation | Export animation of morph target manager with id: " + babylonMorphTargetManager.id, 2);
+
+            var influencesPerFrame = _getTargetManagerAnimationsData(babylonMorphTargetManager);
+            var frames = new List<int>(influencesPerFrame.Keys);
+            frames.Sort(); // Mandatory otherwise gltf loader of babylon doesn't understand
+
+            // Target
+            var gltfTarget = new GLTFChannelTarget
+            {
+                node = gltfNode.index
+            };
+            gltfTarget.path = "weights";
+
+            // 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 frame in frames)
+            {
+                var inputValue = frame / FPS_FACTOR;
+                // Store values as bytes
+                accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
+                // Update min and max values
+                GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
+            }
+            accessorInput.count = influencesPerFrame.Count;
+
+            // --- Output ---
+            GLTFAccessor accessorOutput = GLTFBufferService.Instance.CreateAccessor(
+                gltf,
+                GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
+                "accessorAnimationWeights",
+                GLTFAccessor.ComponentType.FLOAT,
+                GLTFAccessor.TypeEnum.SCALAR
+            );
+            // Populate accessor
+            foreach (var frame in frames)
+            {
+                var outputValues = influencesPerFrame[frame];
+                // Store values as bytes
+                foreach (var outputValue in outputValues)
+                {
+                    accessorOutput.count++;
+                    accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
+                }
+            }
+
+            // 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);
+
+            return true;
+        }
+
+        private bool _isBabylonMorphTargetManagerAnimationValid(BabylonMorphTargetManager babylonMorphTargetManager)
+        {
+            bool hasAnimation = false;
+            bool areAnimationsValid = true;
+            foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
+            {
+                if (babylonMorphTarget.animations != null && babylonMorphTarget.animations.Length > 0)
+                {
+                    hasAnimation = true;
+
+                    // Ensure target has only one animation
+                    if (babylonMorphTarget.animations.Length > 1)
+                    {
+                        areAnimationsValid = false;
+                        RaiseWarning("GLTFExporter.Animation | Only one animation is supported for morph targets", 3);
+                        continue;
+                    }
+
+                    // Ensure the target animation property is 'influence'
+                    bool targetHasInfluence = false;
+                    foreach (BabylonAnimation babylonAnimation in babylonMorphTarget.animations)
+                    {
+                        if (babylonAnimation.property == "influence")
+                        {
+                            targetHasInfluence = true;
+                        }
+                    }
+                    if (targetHasInfluence == false)
+                    {
+                        areAnimationsValid = false;
+                        RaiseWarning("GLTFExporter.Animation | Only 'influence' animation is supported for morph targets", 3);
+                        continue;
+                    }
+                }
+            }
+
+            return hasAnimation && areAnimationsValid;
+        }
+
+        /// <summary>
+        /// The keys of each BabylonMorphTarget animation ARE NOT assumed to be identical.
+        /// This function merges together all keys and binds to each an influence value for all targets.
+        /// A target influence value is automatically computed when necessary.
+        /// Computation rules are:
+        /// - linear interpolation between target key range
+        /// - constant value outside target key range
+        /// </summary>
+        /// <example>
+        /// When:
+        /// animation1.keys = {0, 25, 50, 100}
+        /// animation2.keys = {50, 75, 100}
+        /// 
+        /// Gives:
+        /// mergedKeys = {0, 25, 50, 100, 75}
+        /// range1=[0, 100]
+        /// range2=[50, 100]
+        /// for animation1, the value associated to key=75 is the interpolation of its values between 50 and 100
+        /// for animation2, the value associated to key=0 is equal to the one at key=50 since 0 is out of range [50, 100] (same for key=25)</example>
+        /// <param name="babylonMorphTargetManager"></param>
+        /// <returns>A map which for each frame, gives the influence value of all targets</returns>
+        private Dictionary<int, List<float>> _getTargetManagerAnimationsData(BabylonMorphTargetManager babylonMorphTargetManager)
+        {
+            // Merge all keys into a single set (no duplicated frame)
+            var mergedFrames = new HashSet<int>();
+            foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
+            {
+                if (babylonMorphTarget.animations != null)
+                {
+                    var animation = babylonMorphTarget.animations[0];
+                    foreach (BabylonAnimationKey animationKey in animation.keys)
+                    {
+                        mergedFrames.Add(animationKey.frame);
+                    }
+                }
+            }
+
+            // For each frame, gives the influence value of all targets (gltf structure)
+            var influencesPerFrame = new Dictionary<int, List<float>>();
+            foreach (var frame in mergedFrames)
+            {
+                influencesPerFrame.Add(frame, new List<float>());
+            }
+            foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
+            {
+                // For a given target, for each frame, gives the influence value of the target (babylon structure)
+                var influencePerFrameForTarget = new Dictionary<int, float>();
+
+                if (babylonMorphTarget.animations != null && babylonMorphTarget.animations.Length > 0)
+                {
+                    var animation = babylonMorphTarget.animations[0];
+
+                    if (animation.keys.Length == 1)
+                    {
+                        // Same influence for all frames
+                        var influence = animation.keys[0].values[0];
+                        foreach (var frame in mergedFrames)
+                        {
+                            influencePerFrameForTarget.Add(frame, influence);
+                        }
+                    }
+                    else
+                    {
+                        // Retreive target animation key range [min, max]
+                        var babylonAnimationKeys = new List<BabylonAnimationKey>(animation.keys);
+                        babylonAnimationKeys.Sort();
+                        var minAnimationKey = babylonAnimationKeys[0];
+                        var maxAnimationKey = babylonAnimationKeys[babylonAnimationKeys.Count - 1];
+                        
+                        foreach (var frame in mergedFrames)
+                        {
+                            // Surround the current frame with closest keys available for the target
+                            BabylonAnimationKey lowerAnimationKey = minAnimationKey;
+                            BabylonAnimationKey upperAnimationKey = maxAnimationKey;
+                            foreach (BabylonAnimationKey animationKey in animation.keys)
+                            {
+                                if (lowerAnimationKey.frame < animationKey.frame && animationKey.frame <= frame)
+                                {
+                                    lowerAnimationKey = animationKey;
+                                }
+                                if (frame <= animationKey.frame && animationKey.frame < upperAnimationKey.frame)
+                                {
+                                    upperAnimationKey = animationKey;
+                                }
+                            }
+
+                            // In case the target has a key for this frame
+                            // or the current frame is out of target animation key range
+                            if (lowerAnimationKey.frame == upperAnimationKey.frame)
+                            {
+                                influencePerFrameForTarget.Add(frame, lowerAnimationKey.values[0]);
+                            }
+                            else
+                            {
+                                // Interpolate influence values
+                                var t = 1.0f * (frame - lowerAnimationKey.frame) / (upperAnimationKey.frame - lowerAnimationKey.frame);
+                                var influence = Tools.Lerp(lowerAnimationKey.values[0], upperAnimationKey.values[0], t);
+                                influencePerFrameForTarget.Add(frame, influence);
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    // Target is not animated
+                    // Fill all frames with 0
+                    foreach (var frame in mergedFrames)
+                    {
+                        influencePerFrameForTarget.Add(frame, 0);
+                    }
+                }
+
+                // Switch from babylon to gltf storage representation
+                foreach (var frame in mergedFrames)
+                {
+                    List<float> influences = influencesPerFrame[frame];
+                    influences.Add(influencePerFrameForTarget[frame]);
+                }
+            }
+
+            return influencesPerFrame;
+        }
+
+        private string _getTargetPath(string babylonProperty)
+        {
+            switch (babylonProperty)
+            {
+                case "position":
+                    return "translation";
+                case "rotationQuaternion":
+                    return "rotation";
+                case "scaling":
+                    return "scale";
+                default:
+                    return null;
+            }
+        }
+    }
+}

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

@@ -54,6 +54,8 @@ namespace Max2Babylon
             // No scaling defined for babylon camera. Use identity instead.
             gltfNode.scale = new float[3] { 1, 1, 1 };
 
+            // Animations
+            ExportNodeAnimation(babylonCamera, gltf, gltfNode);
 
             // --- prints ---
 

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

@@ -41,6 +41,9 @@ namespace Max2Babylon
             // No scaling defined for babylon light. Use identity instead.
             gltfNode.scale = new float[3] { 1, 1, 1 };
 
+            // Animations
+            ExportNodeAnimation(babylonLight, gltf, gltfNode);
+
             return gltfNode;
         }
     }

+ 221 - 305
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;
-            //}
 
 
             // --------------------------
@@ -90,92 +81,7 @@ namespace Max2Babylon
             gltfMesh.index = gltf.MeshesList.Count;
             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;
-                }
-            }
+            var weights = new List<float>();
 
             // --------------------------
             // ---- glTF primitives -----
@@ -183,15 +89,6 @@ namespace Max2Babylon
 
             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 +97,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 +120,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,121 +150,249 @@ 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
+                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 => accessorNormals.bytesList.AddRange(BitConverter.GetBytes(n)));
+                accessorNormals.count = globalVerticesSubMesh.Count;
+                
+                // --- Colors ---
                 if (hasColor)
                 {
-                    AddElementsToAccessor(accessorColors, globalVerticesSubMesh.Count);
+                    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 => accessorColors.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    accessorColors.count = globalVerticesSubMesh.Count;
                 }
-                // Vector2
+                
+                // --- UV ---
                 if (hasUV)
                 {
-                    AddElementsToAccessor(accessorUVs, globalVerticesSubMesh.Count);
+                    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 => accessorUVs.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    accessorUVs.count = globalVerticesSubMesh.Count;
                 }
+                
+                // --- UV2 ---
                 if (hasUV2)
                 {
-                    AddElementsToAccessor(accessorUV2s, globalVerticesSubMesh.Count);
+                    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 => accessorUV2s.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    accessorUV2s.count = globalVerticesSubMesh.Count;
+                }
+
+                // Morph targets
+                var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);
+                if (babylonMorphTargetManager != null)
+                {
+                    _exportMorphTargets(babylonMesh, babylonMorphTargetManager, gltf, buffer, meshPrimitive, weights);
                 }
             }
             gltfMesh.primitives = meshPrimitives.ToArray();
-            
-            // Update byte offset of bufferViews
-            GLTFBufferView lastBufferView = null;
-            gltf.BufferViewsList.FindAll(bufferView => bufferView.buffer == buffer.index).ForEach(bufferView =>
+            if (weights.Count > 0)
             {
-                if (lastBufferView != null)
-                {
-                    bufferView.byteOffset = lastBufferView.byteOffset + lastBufferView.byteLength;
-                }
-                lastBufferView = bufferView;
-            });
-
-
-            // --------------------------
-            // --------- Saving ---------
-            // --------------------------
-
-            RaiseMessage("GLTFExporter.Mesh | saving", 2);
+                gltfMesh.weights = weights.ToArray();
+            }
 
-            // BufferView - Scalar
-            gltfIndices.ForEach(n => bufferViewScalar.bytesList.AddRange(BitConverter.GetBytes(n)));
+            return gltfMesh;
+        }
 
-            // BufferView - Vector3
-            globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
+        private BabylonMorphTargetManager GetBabylonMorphTargetManager(BabylonScene babylonScene, BabylonMesh babylonMesh)
+        {
+            if (babylonMesh.morphTargetManagerId.HasValue)
             {
-                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)));
+                if (babylonScene.morphTargetManagers == null)
+                {
+                    RaiseWarning("GLTFExporter.Mesh | morphTargetManagers is not defined", 3);
+                }
+                else
+                {
+                    var babylonMorphTargetManager = babylonScene.morphTargetManagers.ElementAtOrDefault(babylonMesh.morphTargetManagerId.Value);
 
-                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)));
-            });
+                    if (babylonMorphTargetManager == null)
+                    {
+                        RaiseWarning($"GLTFExporter.Mesh | morphTargetManager with index {babylonMesh.morphTargetManagerId.Value} not found", 3);
+                    }
+                    return babylonMorphTargetManager;
+                }
+            }
+            return null;
+        }
 
-            // BufferView - Vector4
-            globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
+        private void _exportMorphTargets(BabylonMesh babylonMesh, BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFBuffer buffer, GLTFMeshPrimitive meshPrimitive, List<float> weights)
+        {
+            var gltfMorphTargets = new List<GLTFMorphTarget>();
+            foreach (var babylonMorphTarget in babylonMorphTargetManager.targets)
             {
-                if (hasColor)
+                var gltfMorphTarget = new GLTFMorphTarget();
+
+                // Positions
+                if (babylonMorphTarget.positions != null)
                 {
-                    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)));
+                    var accessorTargetPositions = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
+                        "accessorTargetPositions",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.VEC3
+                    );
+                    gltfMorphTarget.Add(GLTFMorphTarget.Attribute.POSITION.ToString(), accessorTargetPositions.index);
+                    // Populate accessor
+                    accessorTargetPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
+                    accessorTargetPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
+                    for (int indexPosition = 0; indexPosition < babylonMorphTarget.positions.Length; indexPosition += 3)
+                    {
+                        var positionTarget = _subArray(babylonMorphTarget.positions, indexPosition, 3);
+
+                        // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
+                        var positionMesh = _subArray(babylonMesh.positions, indexPosition, 3);
+                        for (int indexCoordinate = 0; indexCoordinate < positionTarget.Length; indexCoordinate++)
+                        {
+                            positionTarget[indexCoordinate] = positionTarget[indexCoordinate] - positionMesh[indexCoordinate];
+                        }
+
+                        // Store values as bytes
+                        foreach (var coordinate in positionTarget)
+                        {
+                            accessorTargetPositions.bytesList.AddRange(BitConverter.GetBytes(coordinate));
+                        }
+                        // Update min and max values
+                        GLTFBufferService.UpdateMinMaxAccessor(accessorTargetPositions, positionTarget);
+                    }
+                    accessorTargetPositions.count = babylonMorphTarget.positions.Length / 3;
                 }
-            });
 
-            // BufferView - Vector2
-            globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
-            {
-                if (hasUV)
+                // Normals
+                if (babylonMorphTarget.normals != null)
                 {
-                    List<float> uvs = globalVerticesSubMesh.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList();
-                    uvs.ForEach(n => bufferViewFloatVec2.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    var accessorTargetNormals = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
+                        "accessorTargetNormals",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.VEC3
+                    );
+                    gltfMorphTarget.Add(GLTFMorphTarget.Attribute.NORMAL.ToString(), accessorTargetNormals.index);
+                    // Populate accessor
+                    for (int indexNormal = 0; indexNormal < babylonMorphTarget.positions.Length; indexNormal += 3)
+                    {
+                        var normalTarget = _subArray(babylonMorphTarget.normals, indexNormal, 3);
+
+                        // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
+                        var normalMesh = _subArray(babylonMesh.normals, indexNormal, 3);
+                        for (int indexCoordinate = 0; indexCoordinate < normalTarget.Length; indexCoordinate++)
+                        {
+                            normalTarget[indexCoordinate] = normalTarget[indexCoordinate] - normalMesh[indexCoordinate];
+                        }
+
+                        // Store values as bytes
+                        foreach (var coordinate in normalTarget)
+                        {
+                            accessorTargetNormals.bytesList.AddRange(BitConverter.GetBytes(coordinate));
+                        }
+                    }
+                    accessorTargetNormals.count = babylonMorphTarget.normals.Length / 3;
                 }
 
-                if (hasUV2)
+                if (gltfMorphTarget.Count > 0)
                 {
-                    List<float> uvs2 = globalVerticesSubMesh.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList();
-                    uvs2.ForEach(n => bufferViewFloatVec2.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    gltfMorphTargets.Add(gltfMorphTarget);
+                    weights.Add(babylonMorphTarget.influence);
                 }
-            });
-
-            //// 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));
-            //}
+            }
+            if (gltfMorphTargets.Count > 0)
+            {
+                meshPrimitive.targets = gltfMorphTargets.ToArray();
+            }
+        }
 
-            return gltfMesh;
+        private float[] _subArray(float[] array, int startIndex, int count)
+        {
+            var result = new float[count];
+            for (int i = 0; i < count; i++)
+            {
+                result[i] = array[startIndex + i];
+            }
+            return result;
         }
 
         private IPoint2 createIPoint2(float[] array, int index)
@@ -485,16 +412,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();
-        }
     }
 }

+ 29 - 9
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.cs

@@ -84,7 +84,7 @@ namespace Max2Babylon
                 idGroupInstance = -1
             };
             scene.NodesList.Clear(); // Only root node is listed in node list
-            GLTFNode gltfRootNode = ExportAbstractMesh(rootNode, gltf, null);
+            GLTFNode gltfRootNode = ExportAbstractMesh(rootNode, gltf, null, null);
             gltfRootNode.ChildrenList.AddRange(tmpNodesList);
 
             // Materials
@@ -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));
                 });
             }
 
@@ -234,7 +254,7 @@ namespace Max2Babylon
             if (type == typeof(BabylonAbstractMesh) ||
                 type.IsSubclassOf(typeof(BabylonAbstractMesh)))
             {
-                gltfNode = ExportAbstractMesh(babylonNode as BabylonAbstractMesh, gltf, gltfParentNode);
+                gltfNode = ExportAbstractMesh(babylonNode as BabylonAbstractMesh, gltf, gltfParentNode, babylonScene);
             }
             else if (type == typeof(BabylonCamera))
             {

+ 203 - 84
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Mesh.cs

@@ -43,6 +43,9 @@ namespace Max2Babylon
             // Position / rotation / scaling / hierarchy
             exportNode(babylonMesh, meshNode, scene, babylonScene);
 
+            // Animations
+            exportAnimation(babylonMesh, meshNode);
+
             babylonScene.MeshesList.Add(babylonMesh);
 
             return babylonMesh;
@@ -306,64 +309,8 @@ namespace Max2Babylon
                 var optimizeVertices = meshNode.MaxNode.GetBoolProperty("babylonjs_optimizevertices");
 
                 // Compute normals
-                List<GlobalVertex>[] verticesAlreadyExported = null;
-
-                if (optimizeVertices)
-                {
-                    verticesAlreadyExported = new List<GlobalVertex>[unskinnedMesh.NumberOfVerts];
-                }
-
                 var subMeshes = new List<BabylonSubMesh>();
-                var indexStart = 0;
-
-
-                for (int i = 0; i < multiMatsCount; ++i)
-                {
-                    int materialId = meshNode.NodeMaterial?.GetMaterialID(i) ?? 0;
-                    var indexCount = 0;
-                    var minVertexIndex = int.MaxValue;
-                    var maxVertexIndex = int.MinValue;
-                    var subMesh = new BabylonSubMesh { indexStart = indexStart, materialIndex = i };
-
-                    if (multiMatsCount == 1)
-                    {
-                        for (int j = 0; j < unskinnedMesh.NumberOfFaces; ++j)
-                        {
-                            var face = unskinnedMesh.GetFace(j);
-                            ExtractFace(skin, unskinnedMesh, vertices, indices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, ref indexCount, ref minVertexIndex, ref maxVertexIndex, face, boneIds);
-                        }
-                    }
-                    else
-                    {
-                        ITab<IFaceEx> materialFaces = unskinnedMesh.GetFacesFromMatID(materialId);
-                        for (int j = 0; j < materialFaces.Count; ++j)
-                        {
-#if MAX2017
-                            var faceIndexer = j;
-#else
-                            var faceIndexer = new IntPtr(j);
-#endif
-                            var face = materialFaces[faceIndexer];
-
-#if !MAX2017
-                            Marshal.FreeHGlobal(faceIndexer);
-#endif
-                            ExtractFace(skin, unskinnedMesh, vertices, indices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, ref indexCount, ref minVertexIndex, ref maxVertexIndex, face, boneIds);
-                        }
-                    }
-
-                    if (indexCount != 0)
-                    {
-
-                        subMesh.indexCount = indexCount;
-                        subMesh.verticesStart = minVertexIndex;
-                        subMesh.verticesCount = maxVertexIndex - minVertexIndex + 1;
-
-                        indexStart += indexCount;
-
-                        subMeshes.Add(subMesh);
-                    }
-                }
+                ExtractGeometry(vertices, indices, subMeshes, boneIds, skin, unskinnedMesh, hasUV, hasUV2, hasColor, hasAlpha, optimizeVertices, multiMatsCount, meshNode);
 
                 if (vertices.Count >= 65536)
                 {
@@ -412,6 +359,80 @@ namespace Max2Babylon
 
                 // Buffers - Indices
                 babylonMesh.indices = indices.ToArray();
+
+                // ------------------------
+                // ---- Morph targets -----
+                // ------------------------
+
+                // Retreive modifiers with morpher flag
+                List<IIGameModifier> modifiers = new List<IIGameModifier>();
+                for (int i = 0; i < meshNode.IGameObject.NumModifiers; i++)
+                {
+                    var modifier = meshNode.IGameObject.GetIGameModifier(i);
+                    if (modifier.ModifierType == Autodesk.Max.IGameModifier.ModType.Morpher)
+                    {
+                        modifiers.Add(modifier);
+                    }
+                }
+
+                // Cast modifiers to morphers
+                List<IIGameMorpher> morphers = modifiers.ConvertAll(new Converter<IIGameModifier, IIGameMorpher>(modifier => modifier.AsGameMorpher()));
+
+                var hasMorphTarget = false;
+                morphers.ForEach(morpher =>
+                {
+                    if (morpher.NumberOfMorphTargets > 0)
+                    {
+                        hasMorphTarget = true;
+                    }
+                });
+
+                if (hasMorphTarget)
+                {
+                    RaiseMessage("Export morph targets", 2);
+
+                    // Morph Target Manager
+                    var babylonMorphTargetManager = new BabylonMorphTargetManager();
+                    babylonScene.MorphTargetManagersList.Add(babylonMorphTargetManager);
+                    babylonMesh.morphTargetManagerId = babylonMorphTargetManager.id;
+
+                    // Morph Targets
+                    var babylonMorphTargets = new List<BabylonMorphTarget>();
+                    // All morphers are considered identical
+                    // Their targets are concatenated
+                    morphers.ForEach(morpher =>
+                    {
+                        for (int i = 0; i < morpher.NumberOfMorphTargets; i++)
+                        {
+                            // Morph target
+                            var maxMorphTarget = morpher.GetMorphTarget(i);
+                            var babylonMorphTarget = new BabylonMorphTarget
+                            {
+                                name = maxMorphTarget.Name
+                            };
+                            babylonMorphTargets.Add(babylonMorphTarget);
+
+                            // TODO - Influence
+                            babylonMorphTarget.influence = 0f;
+
+                            // Target geometry
+                            var targetVertices = ExtractVertices(maxMorphTarget, optimizeVertices);
+                            babylonMorphTarget.positions = targetVertices.SelectMany(v => new[] { v.Position.X, v.Position.Y, v.Position.Z }).ToArray();
+                            babylonMorphTarget.normals = targetVertices.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToArray();
+
+                            // Animations
+                            var animations = new List<BabylonAnimation>();
+                            var morphWeight = morpher.GetMorphWeight(i);
+                            ExportFloatGameController(morphWeight, "influence", animations);
+                            if (animations.Count > 0)
+                            {
+                                babylonMorphTarget.animations = animations.ToArray();
+                            }
+                        }
+                    });
+
+                    babylonMorphTargetManager.targets = babylonMorphTargets.ToArray();
+                }
             }
 
             babylonScene.MeshesList.Add(babylonMesh);
@@ -419,6 +440,86 @@ namespace Max2Babylon
             return babylonMesh;
         }
 
+        private List<GlobalVertex> ExtractVertices(IIGameNode maxMorphTarget, bool optimizeVertices)
+        {
+            var gameMesh = maxMorphTarget.IGameObject.AsGameMesh();
+            bool initialized = gameMesh.InitializeData; // needed, the property is in fact a method initializing the exporter that has wrongly been auto 
+                                                        // translated into a property because it has no parameters
+
+            var mtl = maxMorphTarget.NodeMaterial;
+            var multiMatsCount = 1;
+
+            if (mtl != null)
+            {
+                multiMatsCount = Math.Max(mtl.SubMaterialCount, 1);
+            }
+
+            var vertices = new List<GlobalVertex>();
+            ExtractGeometry(vertices, new List<int>(), new List<BabylonSubMesh>(), null, null, gameMesh, false, false, false, false, optimizeVertices, multiMatsCount, maxMorphTarget);
+            return vertices;
+        }
+
+        private void ExtractGeometry(List<GlobalVertex> vertices, List<int> indices, List<BabylonSubMesh> subMeshes, List<int> boneIds, IIGameSkin skin, IIGameMesh unskinnedMesh, bool hasUV, bool hasUV2, bool hasColor, bool hasAlpha, bool optimizeVertices, int multiMatsCount, IIGameNode meshNode)
+        {
+            List<GlobalVertex>[] verticesAlreadyExported = null;
+
+            if (optimizeVertices)
+            {
+                verticesAlreadyExported = new List<GlobalVertex>[unskinnedMesh.NumberOfVerts];
+            }
+
+            var indexStart = 0;
+
+
+            for (int i = 0; i < multiMatsCount; ++i)
+            {
+                int materialId = meshNode.NodeMaterial?.GetMaterialID(i) ?? 0;
+                var indexCount = 0;
+                var minVertexIndex = int.MaxValue;
+                var maxVertexIndex = int.MinValue;
+                var subMesh = new BabylonSubMesh { indexStart = indexStart, materialIndex = i };
+
+                if (multiMatsCount == 1)
+                {
+                    for (int j = 0; j < unskinnedMesh.NumberOfFaces; ++j)
+                    {
+                        var face = unskinnedMesh.GetFace(j);
+                        ExtractFace(skin, unskinnedMesh, vertices, indices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, ref indexCount, ref minVertexIndex, ref maxVertexIndex, face, boneIds);
+                    }
+                }
+                else
+                {
+                    ITab<IFaceEx> materialFaces = unskinnedMesh.GetFacesFromMatID(materialId);
+                    for (int j = 0; j < materialFaces.Count; ++j)
+                    {
+#if MAX2017
+                        var faceIndexer = j;
+#else
+                            var faceIndexer = new IntPtr(j);
+#endif
+                        var face = materialFaces[faceIndexer];
+
+#if !MAX2017
+                            Marshal.FreeHGlobal(faceIndexer);
+#endif
+                        ExtractFace(skin, unskinnedMesh, vertices, indices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, ref indexCount, ref minVertexIndex, ref maxVertexIndex, face, boneIds);
+                    }
+                }
+
+                if (indexCount != 0)
+                {
+
+                    subMesh.indexCount = indexCount;
+                    subMesh.verticesStart = minVertexIndex;
+                    subMesh.verticesCount = maxVertexIndex - minVertexIndex + 1;
+
+                    indexStart += indexCount;
+
+                    subMeshes.Add(subMesh);
+                }
+            }
+        }
+
         private void ExtractFace(IIGameSkin skin, IIGameMesh unskinnedMesh, List<GlobalVertex> vertices, List<int> indices, bool hasUV, bool hasUV2, bool hasColor, bool hasAlpha, List<GlobalVertex>[] verticesAlreadyExported, ref int indexCount, ref int minVertexIndex, ref int maxVertexIndex, IFaceEx face, List<int> boneIds)
         {
             var a = CreateGlobalVertex(unskinnedMesh, face, 0, vertices, hasUV, hasUV2, hasColor, hasAlpha, verticesAlreadyExported, skin, boneIds);
@@ -749,41 +850,59 @@ namespace Max2Babylon
 
         public void GenerateCoordinatesAnimations(IIGameNode meshNode, List<BabylonAnimation> animations)
         {
-            ExportVector3Animation("position", animations, key =>
+            if (meshNode.IGameControl.IsAnimated(IGameControlType.Pos) ||
+                meshNode.IGameControl.IsAnimated(IGameControlType.PosX) ||
+                meshNode.IGameControl.IsAnimated(IGameControlType.PosY) ||
+                meshNode.IGameControl.IsAnimated(IGameControlType.PosZ))
             {
-                var worldMatrix = meshNode.GetObjectTM(key);
-                if (meshNode.NodeParent != null)
+                ExportVector3Animation("position", animations, key =>
                 {
-                    var parentWorld = meshNode.NodeParent.GetObjectTM(key);
-                    worldMatrix.MultiplyBy(parentWorld.Inverse);
-                }
-                var trans = worldMatrix.Translation;
-                return new[] { trans.X, trans.Y, trans.Z };
-            });
+                    var worldMatrix = meshNode.GetObjectTM(key);
+                    if (meshNode.NodeParent != null)
+                    {
+                        var parentWorld = meshNode.NodeParent.GetObjectTM(key);
+                        worldMatrix.MultiplyBy(parentWorld.Inverse);
+                    }
+                    var trans = worldMatrix.Translation;
+                    return new[] { trans.X, trans.Y, trans.Z };
+                });
+            }
 
-            ExportQuaternionAnimation("rotationQuaternion", animations, key =>
+            if (meshNode.IGameControl.IsAnimated(IGameControlType.Rot) ||
+                meshNode.IGameControl.IsAnimated(IGameControlType.EulerX) ||
+                meshNode.IGameControl.IsAnimated(IGameControlType.EulerY) ||
+                meshNode.IGameControl.IsAnimated(IGameControlType.EulerZ))
             {
-                var worldMatrix = meshNode.GetObjectTM(key);
-                if (meshNode.NodeParent != null)
+                ExportQuaternionAnimation("rotationQuaternion", animations, key =>
                 {
-                    var parentWorld = meshNode.NodeParent.GetObjectTM(key);
-                    worldMatrix.MultiplyBy(parentWorld.Inverse);
-                }
-                var rot = worldMatrix.Rotation;
-                return new[] { rot.X, rot.Y, rot.Z, -rot.W };
-            });
+                    var worldMatrix = meshNode.GetObjectTM(key);
+                    if (meshNode.NodeParent != null)
+                    {
+                        var parentWorld = meshNode.NodeParent.GetObjectTM(key);
+                        worldMatrix.MultiplyBy(parentWorld.Inverse);
+                    }
+
 
-            ExportVector3Animation("scaling", animations, key =>
+                    var rot = worldMatrix.Rotation;
+                    return new[] { rot.X, rot.Y, rot.Z, -rot.W };
+                });
+            }
+
+            if (meshNode.IGameControl.IsAnimated(IGameControlType.Scale))
             {
-                var worldMatrix = meshNode.GetObjectTM(key);
-                if (meshNode.NodeParent != null)
+                ExportVector3Animation("scaling", animations, key =>
                 {
-                    var parentWorld = meshNode.NodeParent.GetObjectTM(key);
-                    worldMatrix.MultiplyBy(parentWorld.Inverse);
-                }
-                var scale = worldMatrix.Scaling;
-                return new[] { scale.X, scale.Y, scale.Z };
-            });
+                    var worldMatrix = meshNode.GetObjectTM(key);
+                    if (meshNode.NodeParent != null)
+                    {
+                        var parentWorld = meshNode.NodeParent.GetObjectTM(key);
+                        worldMatrix.MultiplyBy(parentWorld.Inverse);
+                    }
+                    var scale = worldMatrix.Scaling;
+
+                    return new[] { scale.X, scale.Y, scale.Z };
+                });
+            }
         }
     }
 }

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

@@ -167,6 +167,8 @@ namespace Max2Babylon
             var progression = 10.0f;
             ReportProgressChanged((int)progression);
             referencedMaterials.Clear();
+            // Reseting is optionnal. It makes each morph target manager export starts from id = 0.
+            BabylonMorphTargetManager.Reset();
             foreach (var maxRootNode in maxRootNodes)
             {
                 exportNodeRec(maxRootNode, babylonScene, gameScene);
@@ -320,6 +322,11 @@ namespace Max2Babylon
                 case Autodesk.Max.IGameObject.ObjectTypes.Light:
                     babylonNode = ExportLight(maxGameScene, maxGameNode, babylonScene);
                     break;
+                case Autodesk.Max.IGameObject.ObjectTypes.Unknown:
+                    // Create a dummy (empty mesh) when type is unknown
+                    // An example of unknown type object is the target of target light or camera
+                    babylonNode = ExportDummy(maxGameScene, maxGameNode, babylonScene);
+                    break;
                 default:
                     // The type of node is not exportable (helper, spline, xref...)
                     hasExporter = false;

+ 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;
+            }
+        }
+    }
+}

+ 18 - 1
Exporters/3ds Max/Max2Babylon/Tools/Tools.cs

@@ -13,6 +13,11 @@ namespace Max2Babylon
 {
     public static class Tools
     {
+        public static float Lerp(float min, float max, float t)
+        {
+            return min + (max - min) * t;
+        }
+
         // -------------------------
         // -- IIPropertyContainer --
         // -------------------------
@@ -105,6 +110,18 @@ namespace Max2Babylon
             }
         }
 
+        public static IIGameMorpher AsGameMorpher(this IIGameModifier obj)
+        {
+            var type = GetWrappersAssembly().GetType("Autodesk.Max.Wrappers.IGameMorpher");
+            var constructor = type.GetConstructors()[0];
+            // var pointerType = GetWrappersAssembly().GetType("IGameCamera");
+            unsafe
+            {
+                var voidPtr = obj.GetNativeHandle().ToPointer();
+                return (IIGameMorpher)constructor.Invoke(new object[] { obj.GetNativeHandle(), false });
+            }
+        }
+
         public const float Epsilon = 0.001f;
 
         public static IPoint3 XAxis { get { return Loader.Global.Point3.Create(1, 0, 0); } }
@@ -118,7 +135,7 @@ namespace Max2Babylon
         }
 
         public static IMatrix3 Identity { get { return Loader.Global.Matrix3.Create(XAxis, YAxis, ZAxis, Origin); } }
-        
+
         public static Vector3 ToEulerAngles(this IQuat q)
         {
             // Store the Euler angles in radians

+ 12 - 0
src/Morph/babylon.morphTarget.ts

@@ -80,6 +80,9 @@ module BABYLON {
                 serializationObject.tangents = Array.prototype.slice.call(this.getTangents());
             }
 
+            // Animations
+            Animation.AppendSerializedAnimations(this, serializationObject);
+
             return serializationObject;
         }
 
@@ -96,6 +99,15 @@ module BABYLON {
                 result.setTangents(serializationObject.tangents);
             }
 
+            // Animations
+            if (serializationObject.animations) {
+                for (var animationIndex = 0; animationIndex < serializationObject.animations.length; animationIndex++) {
+                    var parsedAnimation = serializationObject.animations[animationIndex];
+
+                    result.animations.push(Animation.Parse(parsedAnimation));
+                }
+            }
+
             return result;
         }