فهرست منبع

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

David Catuhe 8 سال پیش
والد
کامیت
525456bcf7
20فایلهای تغییر یافته به همراه1031 افزوده شده و 500 حذف شده
  1. 3 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonAbstractMesh.cs
  2. 98 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonColor3.cs
  3. 1 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonExport.Entities.csproj
  4. 2 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonMesh.cs
  5. 45 1
      Exporters/3ds Max/BabylonExport.Entities/BabylonQuaternion.cs
  6. 4 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonStandardMaterial.cs
  7. 7 9
      Exporters/3ds Max/BabylonExport.Entities/BabylonVector3.cs
  8. 2 1
      Exporters/3ds Max/GltfExport.Entities/GLTFMesh.cs
  9. 64 0
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.AbstractMesh.cs
  10. 1 1
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Camera.cs
  11. 201 13
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Material.cs
  12. 13 53
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Mesh.cs
  13. 33 27
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Texture.cs
  14. 72 36
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.cs
  15. 1 0
      Exporters/3ds Max/Max2Babylon/2017/Max2Babylon2017.csproj
  16. 20 7
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Camera.cs
  17. 15 3
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Light.cs
  18. 234 250
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Mesh.cs
  19. 211 95
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.cs
  20. 4 4
      dist/preview release/what's new.md

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

@@ -20,6 +20,9 @@ namespace BabylonExport.Entities
         [DataMember]
         public BabylonActions actions { get; set; }
 
+        // Identifier shared between a mesh and its instances
+        public int idGroupInstance;
+
         public BabylonAbstractMesh()
         {
             position = new[] { 0f, 0f, 0f };

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

@@ -0,0 +1,98 @@
+using System;
+using System.Drawing;
+using System.Runtime.Serialization;
+
+namespace BabylonExport.Entities
+{
+    public class BabylonColor3
+    {
+        public float r;
+        public float g;
+        public float b;
+
+        public BabylonColor3(float r = 0, float g = 0, float b = 0)
+        {
+            this.r = r;
+            this.g = g;
+            this.b = b;
+        }
+
+        public BabylonColor3(float[] array)
+        {
+            if (array.Length >= 3)
+            {
+                this.r = array[0];
+                this.g = array[1];
+                this.b = array[2];
+            }
+        }
+
+        public BabylonColor3(Color color)
+        {
+            this.r = color.R / 255.0f;
+            this.g = color.G / 255.0f;
+            this.b = color.B / 255.0f;
+        }
+
+        public override string ToString()
+        {
+            return "{ r=" + r + ", g=" + g + ", b=" + b + "}";
+        }
+
+        public BabylonColor3 clamp(float min = 0, float max = 1)
+        {
+            this.r = ClampScalar(this.r, min, max);
+            this.g = ClampScalar(this.g, min, max);
+            this.b = ClampScalar(this.b, min, max);
+            return this;
+        }
+
+        public float getPerceivedBrightness()
+        {
+            return (float)Math.Sqrt(0.299 * this.r * this.r + 0.587 * this.g * this.g + 0.114 * this.b * this.b);
+        }
+
+        public float getMaxComponent()
+        {
+            return Math.Max(this.r, Math.Max(this.g, this.b));
+        }
+
+        /**
+         * Multiplies in place each rgb value by scale.  
+         * Returns the updated Color3.  
+         */
+        public BabylonColor3 scale(float scale)
+        {
+            return new BabylonColor3(this.r * scale, this.g * scale, this.b * scale);
+        }
+
+        /**
+         * Returns a new Color3 set with the subtracted values of the passed one from the current Color3.    
+         */
+        public BabylonColor3 subtract(BabylonColor3 right)
+        {
+            return new BabylonColor3(this.r - right.r, this.g - right.g, this.b - right.b);
+        }
+
+        /**
+         * Creates a new Color3 with values linearly interpolated of "amount" between the start Color3 and the end Color3.  
+         */
+        public static BabylonColor3 Lerp(BabylonColor3 start, BabylonColor3 end, float amount)
+        {
+            var r = start.r + ((end.r - start.r) * amount);
+            var g = start.g + ((end.g - start.g) * amount);
+            var b = start.b + ((end.b - start.b) * amount);
+            return new BabylonColor3(r, g, b);
+        }
+
+        /**
+         * Returns the value itself if it's between min and max.  
+         * Returns min if the value is lower than min.
+         * Returns max if the value is greater than max.  
+         */
+        private static float ClampScalar(float value, float min = 0, float max = 1)
+        {
+            return Math.Min(max, Math.Max(min, value));
+        }
+    }
+}

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

@@ -70,6 +70,7 @@
     <Compile Include="BabylonNode.cs" />
     <Compile Include="BabylonPBRMaterial.cs" />
     <Compile Include="BabylonShaderMaterial.cs" />
+    <Compile Include="BabylonColor3.cs" />
     <Compile Include="BabylonStandardMaterial.cs" />
     <Compile Include="BabylonAbstractMesh.cs" />
     <Compile Include="BabylonMesh.cs" />

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

@@ -110,6 +110,8 @@ namespace BabylonExport.Entities
         [DataMember]
         public string tags { get; set; }
 
+        public bool isDummy = false;
+
         public BabylonMesh()
         {
             isEnabled = true;

+ 45 - 1
Exporters/3ds Max/BabylonExport.Entities/BabylonQuaternion.cs

@@ -1,4 +1,6 @@
-namespace BabylonExport.Entities
+using System;
+
+namespace BabylonExport.Entities
 {
     public class BabylonQuaternion
     {
@@ -11,5 +13,47 @@
         {
             return new [] {X, Y, Z, W};
         }
+
+        /**
+         * Copy / pasted from babylon 
+         */
+        public BabylonVector3 toEulerAngles()
+        {
+            var result = new BabylonVector3();
+
+            var qz = this.Z;
+            var qx = this.X;
+            var qy = this.Y;
+            var qw = this.W;
+
+            var sqw = qw * qw;
+            var sqz = qz * qz;
+            var sqx = qx * qx;
+            var sqy = qy * qy;
+
+            var zAxisY = qy * qz - qx * qw;
+            var limit = .4999999;
+
+            if (zAxisY< -limit) {
+                result.Y = (float) (2 * Math.Atan2(qy, qw));
+                result.X = (float) Math.PI / 2;
+                result.Z = 0;
+            } else if (zAxisY > limit) {
+                result.Y = (float) (2 * Math.Atan2(qy, qw));
+                result.X = (float) -Math.PI / 2;
+                result.Z = 0;
+            } else {
+                result.Z = (float)Math.Atan2(2.0 * (qx* qy + qz* qw), (-sqz - sqx + sqy + sqw));
+                result.X = (float)Math.Asin(-2.0 * (qz* qy - qx* qw));
+                result.Y = (float)Math.Atan2(2.0 * (qz* qx + qy* qw), (sqz - sqx - sqy + sqw));
+            }
+
+            return result;
+        }
+
+        public override string ToString()
+        {
+            return "{ X=" + X + ", Y=" + Y + ", Z=" + Z + ", W=" + W + " }";
+        }
     }
 }

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

@@ -77,6 +77,9 @@ namespace BabylonExport.Entities
         [DataMember]
         public int maxSimultaneousLights { get; set; }
 
+        [DataMember]
+        public bool useGlossinessFromSpecularMapAlpha { get; set; }
+
         public BabylonStandardMaterial() : base()
         {
             SetCustomType("BABYLON.StandardMaterial");
@@ -89,6 +92,7 @@ namespace BabylonExport.Entities
             useSpecularOverAlpha = true;
             useEmissiveAsIllumination = false;
             linkEmissiveWithDiffuse = false;
+            useGlossinessFromSpecularMapAlpha = false;
         }
 
         public void SetCustomType(string type)

+ 7 - 9
Exporters/3ds Max/BabylonExport.Entities/BabylonVector3.cs

@@ -38,16 +38,9 @@ namespace BabylonExport.Entities
             return new BabylonVector3 { X = a.X * b, Y = a.Y * b, Z = a.Z * b };
         }
 
-        public BabylonQuaternion toQuaternionGltf()
+        public BabylonQuaternion toQuaternion()
         {
-            BabylonQuaternion babylonQuaternion = RotationYawPitchRollToRefBabylon(X, -Y, -Z);
-            // Doing following computation is ugly but works
-            // The goal is to switch from left to right handed coordinate system
-            // Swap X and Y
-            var tmp = babylonQuaternion.X;
-            babylonQuaternion.X = babylonQuaternion.Y;
-            babylonQuaternion.Y = tmp;
-            return babylonQuaternion;
+            return RotationYawPitchRollToRefBabylon(Y, X, Z);
         }
 
         /**
@@ -75,5 +68,10 @@ namespace BabylonExport.Entities
             result.W = (float)((cosYaw * cosPitch * cosRoll) + (sinYaw * sinPitch * sinRoll));
             return result;
         }
+
+        public override string ToString()
+        {
+            return "{ X=" + X + ", Y=" + Y + ", Z=" + Z + " }";
+        }
     }
 }

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

@@ -11,6 +11,7 @@ namespace GLTFExport.Entities
         [DataMember(EmitDefaultValue = false)]
         public float[] weights { get; set; }
 
-        public GLTFNode gltfNode;
+        // Identifier shared between a babylon mesh and its instances
+        public int idGroupInstance;
     }
 }

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

@@ -0,0 +1,64 @@
+using BabylonExport.Entities;
+using GLTFExport.Entities;
+
+namespace Max2Babylon
+{
+    partial class BabylonExporter
+    {
+        private GLTFNode ExportAbstractMesh(BabylonAbstractMesh babylonAbstractMesh, GLTF gltf, GLTFNode gltfParentNode)
+        {
+            RaiseMessage("GLTFExporter.AbstractMesh | Export abstract mesh named: " + babylonAbstractMesh.name, 1);
+
+            // Node
+            var gltfNode = new GLTFNode();
+            gltfNode.name = babylonAbstractMesh.name;
+            gltfNode.index = gltf.NodesList.Count;
+            gltf.NodesList.Add(gltfNode);
+
+            // Hierarchy
+            if (gltfParentNode != null)
+            {
+                RaiseMessage("GLTFExporter.AbstractMesh | Add " + babylonAbstractMesh.name + " as child to " + gltfParentNode.name, 2);
+                gltfParentNode.ChildrenList.Add(gltfNode.index);
+            }
+            else
+            {
+                // It's a root node
+                // Only root nodes are listed in a gltf scene
+                RaiseMessage("GLTFExporter.AbstractMesh | Add " + babylonAbstractMesh.name + " as root node to scene", 2);
+                gltf.scenes[0].NodesList.Add(gltfNode.index);
+            }
+
+            // Transform
+            gltfNode.translation = babylonAbstractMesh.position;
+            // TODO - Choose between this method and the extra root node
+            // Switch from left to right handed coordinate system
+            //gltfNode.translation[0] *= -1;
+            if (babylonAbstractMesh.rotationQuaternion != null)
+            {
+                gltfNode.rotation = babylonAbstractMesh.rotationQuaternion;
+            }
+            else
+            {
+                // Convert rotation vector to quaternion
+                BabylonVector3 rotationVector3 = new BabylonVector3
+                {
+                    X = babylonAbstractMesh.rotation[0],
+                    Y = babylonAbstractMesh.rotation[1],
+                    Z = babylonAbstractMesh.rotation[2]
+                };
+                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
+            }
+            gltfNode.scale = babylonAbstractMesh.scaling;
+
+            // Mesh
+            var gltfMesh = gltf.MeshesList.Find(_gltfMesh => _gltfMesh.idGroupInstance == babylonAbstractMesh.idGroupInstance);
+            if (gltfMesh != null)
+            {
+                gltfNode.mesh = gltfMesh.index;
+            }
+
+            return gltfNode;
+        }
+    }
+}

+ 1 - 1
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Camera.cs

@@ -52,7 +52,7 @@ namespace Max2Babylon
                     Y = babylonCamera.rotation[1],
                     Z = babylonCamera.rotation[2]
                 };
-                gltfNode.rotation = rotationVector3.toQuaternionGltf().ToArray();
+                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
             }
             // No scaling defined for babylon camera. Use identity instead.
             gltfNode.scale = new float[3] { 1, 1, 1 };

+ 201 - 13
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Material.cs

@@ -1,5 +1,8 @@
 using BabylonExport.Entities;
 using GLTFExport.Entities;
+using System;
+using System.Drawing;
+using System.IO;
 
 namespace Max2Babylon
 {
@@ -23,6 +26,7 @@ namespace Max2Babylon
                 RaiseMessage("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
                 RaiseMessage("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3);
                 RaiseMessage("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3);
+                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 3);
 
                 // Ambient
                 for (int i = 0; i < babylonStandardMaterial.ambient.Length; i++)
@@ -113,28 +117,126 @@ namespace Max2Babylon
                 var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
                 gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;
 
-                // TODO - Retreive diffuse or albedo?
+                // --- Global ---
+
+                SpecularGlossiness _specularGlossiness = new SpecularGlossiness
+                {
+                    diffuse = new BabylonColor3(babylonStandardMaterial.diffuse),
+                    opacity = babylonMaterial.alpha,
+                    specular = new BabylonColor3(babylonStandardMaterial.specular),
+                    glossiness = babylonStandardMaterial.specularPower / 256
+                };
+
+                MetallicRoughness _metallicRoughness = ConvertToMetallicRoughness(_specularGlossiness, true);
+
                 // Base color
-                var babylonDiffuseColor = babylonStandardMaterial.diffuse;
                 gltfPbrMetallicRoughness.baseColorFactor = new float[4]
                 {
-                    babylonDiffuseColor[0],
-                    babylonDiffuseColor[1],
-                    babylonDiffuseColor[2],
-                    babylonMaterial.alpha
+                    _metallicRoughness.baseColor.r,
+                    _metallicRoughness.baseColor.g,
+                    _metallicRoughness.baseColor.b,
+                    _metallicRoughness.opacity
                 };
-                gltfPbrMetallicRoughness.baseColorTexture = ExportTexture(babylonStandardMaterial.diffuseTexture, gltf);
-                 
-                // TODO - Metallic roughness
-                gltfPbrMetallicRoughness.metallicFactor = 0; // Non metal
-                // TODO - roughnessFactor
-                // TODO - metallicRoughnessTexture
+
+                // Metallic roughness
+                gltfPbrMetallicRoughness.metallicFactor = _metallicRoughness.metallic;
+                gltfPbrMetallicRoughness.roughnessFactor = _metallicRoughness.roughness;
+
+
+                // --- Textures ---
+
+                if (babylonStandardMaterial.diffuseTexture != null)
+                {
+                    Func<string, Bitmap> loadTexture = delegate (string textureName)
+                    {
+                        var pathDiffuse = Path.Combine(gltf.OutputPath, textureName);
+                        if (File.Exists(pathDiffuse))
+                        {
+                            return new Bitmap(pathDiffuse);
+                        }
+                        else
+                        {
+                            RaiseWarning(string.Format("GLTFExporter.Material | Texture {0} not found.", textureName), 2);
+                            return null;
+                        }
+                    };
+
+                    Bitmap diffuseBitmap = loadTexture(babylonStandardMaterial.diffuseTexture.name);
+
+                    if (diffuseBitmap != null)
+                    {
+                        Bitmap specularBitmap = null;
+                        if (babylonStandardMaterial.specularTexture != null)
+                        {
+                            specularBitmap = loadTexture(babylonStandardMaterial.specularTexture.name);
+                        }
+
+                        Bitmap opacityBitmap = null;
+                        if (babylonStandardMaterial.diffuseTexture.hasAlpha == false && babylonStandardMaterial.opacityTexture != null)
+                        {
+                            opacityBitmap = loadTexture(babylonStandardMaterial.opacityTexture.name);
+                        }
+
+                        // Retreive dimension from diffuse map
+                        var width = diffuseBitmap.Width;
+                        var height = diffuseBitmap.Height;
+
+                        // Create base color and metallic+roughness maps
+                        Bitmap baseColorBitmap = new Bitmap(width, height);
+                        Bitmap metallicRoughnessBitmap = new Bitmap(width, height);
+                        for (int x = 0; x < width; x++)
+                        {
+                            for (int y = 0; y < height; y++)
+                            {
+                                var diffuse = diffuseBitmap.GetPixel(x, y);
+                                SpecularGlossiness specularGlossinessTexture = new SpecularGlossiness
+                                {
+                                    diffuse = new BabylonColor3(diffuse),
+                                    opacity = babylonStandardMaterial.diffuseTexture.hasAlpha? diffuse.A / 255.0f :
+                                              opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB ? opacityBitmap.GetPixel(x, y).R / 255.0f :
+                                              opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB == false ? opacityBitmap.GetPixel(x, y).A / 255.0f :
+                                              1,
+                                    specular = specularBitmap != null ? new BabylonColor3(specularBitmap.GetPixel(x, y)) :
+                                               new BabylonColor3(),
+                                    glossiness = babylonStandardMaterial.useGlossinessFromSpecularMapAlpha && specularBitmap != null ? specularBitmap.GetPixel(x, y).A / 255.0f :
+                                                 babylonStandardMaterial.specularPower / 256.0f
+                                };
+
+                                var displayPrints = x == width / 2 && y == height / 2;
+                                MetallicRoughness metallicRoughnessTexture = ConvertToMetallicRoughness(specularGlossinessTexture, displayPrints);
+                                
+                                Color colorBase = Color.FromArgb(
+                                    (int)(metallicRoughnessTexture.opacity * 255),
+                                    (int)(metallicRoughnessTexture.baseColor.r * 255),
+                                    (int)(metallicRoughnessTexture.baseColor.g * 255),
+                                    (int)(metallicRoughnessTexture.baseColor.b * 255)
+                                );
+                                baseColorBitmap.SetPixel(x, y, colorBase);
+
+                                // The metalness values are sampled from the B channel.
+                                // The roughness values are sampled from the G channel.
+                                // These values are linear. If other channels are present (R or A), they are ignored for metallic-roughness calculations.
+                                Color colorMetallicRoughness = Color.FromArgb(
+                                    0,
+                                    (int)(metallicRoughnessTexture.roughness * 255),
+                                    (int)(metallicRoughnessTexture.metallic * 255)
+                                );
+                                metallicRoughnessBitmap.SetPixel(x, y, colorMetallicRoughness);
+                            }
+                        }
+
+                        // Export maps and textures
+                        gltfPbrMetallicRoughness.baseColorTexture = ExportBitmapTexture(babylonStandardMaterial.diffuseTexture, baseColorBitmap, babylonMaterial.name + "_baseColor" + ".png", gltf);
+                        gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportBitmapTexture(babylonStandardMaterial.diffuseTexture, metallicRoughnessBitmap, babylonMaterial.name + "_metallicRoughness" + ".jpg", gltf);
+                    }
+                }
             }
         }
 
         private void getAlphaMode(BabylonStandardMaterial babylonMaterial, out string alphaMode, out float? alphaCutoff)
         {
-            if (babylonMaterial.diffuseTexture != null && babylonMaterial.diffuseTexture.hasAlpha)
+            if ((babylonMaterial.diffuseTexture != null && babylonMaterial.diffuseTexture.hasAlpha) ||
+                 babylonMaterial.opacityTexture != null)
             {
                 // TODO - Babylon standard material is assumed to useAlphaFromDiffuseTexture. If not, the alpha mode is a mask.
                 alphaMode = GLTFMaterial.AlphaMode.BLEND.ToString();
@@ -146,5 +248,91 @@ namespace Max2Babylon
             }
             alphaCutoff = null;
         }
+
+        BabylonColor3 dielectricSpecular = new BabylonColor3(0.04f, 0.04f, 0.04f);
+        const float epsilon = 1e-6f;
+
+        private MetallicRoughness ConvertToMetallicRoughness(SpecularGlossiness specularGlossiness, bool displayPrints = false)
+        {
+            var diffuse = specularGlossiness.diffuse;
+            var opacity = specularGlossiness.opacity;
+            var specular = specularGlossiness.specular;
+            var glossiness = specularGlossiness.glossiness;
+
+            var oneMinusSpecularStrength = 1 - specular.getMaxComponent();
+            var metallic = solveMetallic(diffuse.getPerceivedBrightness(), specular.getPerceivedBrightness(), oneMinusSpecularStrength);
+
+            var diffuseScaleFactor = oneMinusSpecularStrength / (1 - dielectricSpecular.r) / Math.Max(1 - metallic, epsilon);
+            var baseColorFromDiffuse = diffuse.scale(diffuseScaleFactor);
+            var baseColorFromSpecular = specular.subtract(dielectricSpecular.scale(1 - metallic)).scale(1 / Math.Max(metallic, epsilon));
+            var baseColor = BabylonColor3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic).clamp();
+            //var baseColor = baseColorFromDiffuse.clamp();
+
+            if (displayPrints)
+            {
+                RaiseMessage("-----------------------", 3);
+                RaiseMessage("diffuse=" + diffuse, 3);
+                RaiseMessage("opacity=" + opacity, 3);
+                RaiseMessage("specular=" + specular, 3);
+                RaiseMessage("glossiness=" + glossiness, 3);
+
+                RaiseMessage("oneMinusSpecularStrength=" + oneMinusSpecularStrength, 3);
+                RaiseMessage("metallic=" + metallic, 3);
+                RaiseMessage("diffuseScaleFactor=" + diffuseScaleFactor, 3);
+                RaiseMessage("baseColorFromDiffuse=" + baseColorFromDiffuse, 3);
+                RaiseMessage("baseColorFromSpecular=" + baseColorFromSpecular, 3);
+                RaiseMessage("metallic * metallic=" + metallic * metallic, 3);
+                RaiseMessage("baseColor=" + baseColor, 3);
+                RaiseMessage("-----------------------", 3);
+            }
+
+            return new MetallicRoughness
+            {
+                baseColor = baseColor,
+                opacity = opacity,
+                metallic = metallic,
+                roughness = 1 - glossiness
+            };
+        }
+
+        private float solveMetallic(float diffuse, float specular, float oneMinusSpecularStrength)
+        {
+            if (specular < dielectricSpecular.r)
+            {
+                return 0;
+            }
+
+            var a = dielectricSpecular.r;
+            var b = diffuse * oneMinusSpecularStrength / (1 - dielectricSpecular.r) + specular - 2 * dielectricSpecular.r;
+            var c = dielectricSpecular.r - specular;
+            var D = b * b - 4 * a * c;
+            return ClampScalar((float)(-b + Math.Sqrt(D)) / (2 * a), 0, 1);
+        }
+
+        /**
+         * Returns the value itself if it's between min and max.  
+         * Returns min if the value is lower than min.
+         * Returns max if the value is greater than max.  
+         */
+        private static float ClampScalar(float value, float min = 0, float max = 1)
+        {
+            return Math.Min(max, Math.Max(min, value));
+        }
+
+        private class SpecularGlossiness
+        {
+            public BabylonColor3 diffuse;
+            public float opacity;
+            public BabylonColor3 specular;
+            public float glossiness;
+        }
+
+        private class MetallicRoughness
+        {
+            public BabylonColor3 baseColor;
+            public float opacity;
+            public float metallic;
+            public float roughness;
+        }
     }
 }

+ 13 - 53
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Mesh.cs

@@ -10,66 +10,18 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private GLTFNode ExportMesh(BabylonMesh babylonMesh, GLTF gltf, GLTFNode gltfParentNode, BabylonScene babylonScene)
+        private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene)
         {
             RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1);
 
             // --------------------------
-            // ---------- Node ----------
-            // --------------------------
-
-            RaiseMessage("GLTFExporter.Mesh | Node", 2);
-            // Node
-            var gltfNode = new GLTFNode();
-            gltfNode.name = babylonMesh.name;
-            gltfNode.index = gltf.NodesList.Count;
-            gltf.NodesList.Add(gltfNode);
-
-            // Hierarchy
-            if (gltfParentNode != null)
-            {
-                RaiseMessage("GLTFExporter.Mesh | Add " + babylonMesh.name + " as child to " + gltfParentNode.name, 3);
-                gltfParentNode.ChildrenList.Add(gltfNode.index);
-            }
-            else
-            {
-                // It's a root node
-                // Only root nodes are listed in a gltf scene
-                RaiseMessage("GLTFExporter.Mesh | Add " + babylonMesh.name + " as root node to scene", 3);
-                gltf.scenes[0].NodesList.Add(gltfNode.index);
-            }
-
-            // Transform
-            gltfNode.translation = babylonMesh.position;
-            // TODO - Choose between this method and the extra root node
-            // Switch from left to right handed coordinate system
-            //gltfNode.translation[0] *= -1;
-            if (babylonMesh.rotationQuaternion != null)
-            {
-                gltfNode.rotation = babylonMesh.rotationQuaternion;
-            }
-            else
-            {
-                // Convert rotation vector to quaternion
-                BabylonVector3 rotationVector3 = new BabylonVector3
-                {
-                    X = babylonMesh.rotation[0],
-                    Y = babylonMesh.rotation[1],
-                    Z = babylonMesh.rotation[2]
-                };
-                gltfNode.rotation = rotationVector3.toQuaternionGltf().ToArray();
-            }
-            gltfNode.scale = babylonMesh.scaling;
-
-
-            // --------------------------
             // --- Mesh from babylon ----
             // --------------------------
 
             if (babylonMesh.positions == null)
             {
                 RaiseMessage("GLTFExporter.Mesh | Mesh is a dummy", 2);
-                return gltfNode;
+                return null;
             }
 
             RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2);
@@ -137,8 +89,7 @@ namespace Max2Babylon
             var gltfMesh = new GLTFMesh { name = babylonMesh.name };
             gltfMesh.index = gltf.MeshesList.Count;
             gltf.MeshesList.Add(gltfMesh);
-            gltfNode.mesh = gltfMesh.index;
-            gltfMesh.gltfNode = gltfNode;
+            gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
 
             // Buffer
             var buffer = new GLTFBuffer
@@ -395,6 +346,15 @@ namespace Max2Babylon
                 // 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);
@@ -477,7 +437,7 @@ namespace Max2Babylon
                 });
             }
 
-            return gltfNode;
+            return gltfMesh;
         }
 
         private IPoint2 createIPoint2(float[] array, int index)

+ 33 - 27
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Texture.cs

@@ -1,18 +1,47 @@
 using BabylonExport.Entities;
 using GLTFExport.Entities;
+using System.Drawing;
+using System.IO;
 
 namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private GLTFTextureInfo ExportTexture(BabylonTexture babylonTexture, GLTF gltf)
+        /// <summary>
+        /// Export the texture using the parameters of babylonTexture except its name.
+        /// Write the bitmap file
+        /// </summary>
+        /// <param name="babylonTexture"></param>
+        /// <param name="bitmap"></param>
+        /// <param name="name"></param>
+        /// <param name="gltf"></param>
+        /// <returns></returns>
+        private GLTFTextureInfo ExportBitmapTexture(BabylonTexture babylonTexture, Bitmap bitmap, string name, GLTF gltf)
+        {
+            // Copy image to output
+            if (CopyTexturesToOutput)
+            {
+                var absolutePath = Path.Combine(gltf.OutputPath, name);
+                RaiseMessage($"GLTFExporter.Texture | write image '{name}' to '{absolutePath}'", 1);
+                bitmap.Save(absolutePath);
+            }
+
+            return ExportTexture(babylonTexture, gltf, name);
+        }
+
+        private GLTFTextureInfo ExportTexture(BabylonTexture babylonTexture, GLTF gltf, string name = null)
         {
             if (babylonTexture == null)
             {
                 return null;
             }
 
-            RaiseMessage("GLTFExporter.Texture | Export texture named: " + babylonTexture.name, 1);
+            if (name == null)
+            {
+                name = babylonTexture.name;
+            }
+
+            RaiseMessage("GLTFExporter.Texture | Export texture named: " + name, 1);
 
             // --------------------------
             // -------- Sampler ---------
@@ -42,7 +71,7 @@ namespace Max2Babylon
             RaiseMessage("GLTFExporter.Texture | create image", 2);
             GLTFImage gltfImage = new GLTFImage
             {
-                uri = babylonTexture.name
+                uri = name
             };
 
             gltfImage.index = gltf.ImagesList.Count;
@@ -56,7 +85,7 @@ namespace Max2Babylon
             RaiseMessage("GLTFExporter.Texture | create texture", 2);
             var gltfTexture = new GLTFTexture
             {
-                name = babylonTexture.name,
+                name = name,
                 sampler = gltfSampler.index,
                 source = gltfImage.index
             };
@@ -75,29 +104,6 @@ namespace Max2Babylon
 
             // TODO - Animations
 
-            //// Copy image to output
-            //var absolutePath = texture.Map.FullFilePath;
-            //try
-            //{
-            //    if (File.Exists(absolutePath))
-            //    {
-            //        if (CopyTexturesToOutput)
-            //        {
-            //            RaiseMessage("GLTFExporter.Texture | copy image src path = "+ absolutePath + " and dest path = "+ Path.Combine(gltf.OutputPath, gltfTexture.name));
-            //            File.Copy(absolutePath, Path.Combine(gltf.OutputPath, gltfTexture.name), true);
-            //        }
-            //    }
-            //    else
-            //    {
-            //        RaiseWarning(string.Format("Texture {0} not found.", gltfTexture.name), 2);
-            //    }
-
-            //}
-            //catch
-            //{
-            //    // silently fails
-            //}
-
             return gltfTextureInfo;
         }
 

+ 72 - 36
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.cs

@@ -13,14 +13,22 @@ namespace Max2Babylon
     internal partial class BabylonExporter
     {
         List<BabylonMaterial> babylonMaterialsToExport;
-        GLTFNode gltfRootNode;
+
+        private List<BabylonNode> babylonNodes;
 
         public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary)
         {
             RaiseMessage("GLTFExporter | Export outputFile=" + outputFile + " generateBinary=" + generateBinary);
             RaiseMessage("GLTFExporter | Exportation started", Color.Blue);
 
-            ReportProgressChanged(0);
+            float progressionStep;
+            var progression = 0.0f;
+            ReportProgressChanged((int)progression);
+
+            // Initialization
+            initBabylonNodes(babylonScene);
+            babylonMaterialsToExport = new List<BabylonMaterial>();
+
             var gltf = new GLTF(Path.GetDirectoryName(outputFile));
 
             // Asset
@@ -40,16 +48,23 @@ namespace Max2Babylon
             GLTFScene[] scenes = { scene };
             gltf.scenes = scenes;
 
-            // Nodes
-            List<BabylonNode> babylonNodes = getNodes(babylonScene);
+            // Meshes
+            RaiseMessage("GLTFExporter | Exporting meshes");
+            progression = 10.0f;
+            ReportProgressChanged((int)progression);
+            progressionStep = 40.0f / babylonScene.meshes.Length;
+            foreach (var babylonMesh in babylonScene.meshes)
+            {
+                ExportMesh(babylonMesh, gltf, babylonScene);
+                progression += progressionStep;
+                ReportProgressChanged((int)progression);
+                CheckCancelled();
+            }
 
             // Root nodes
             RaiseMessage("GLTFExporter | Exporting nodes");
             List<BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);
-            var progressionStep = 80.0f / babylonRootNodes.Count;
-            var progression = 10.0f;
-            ReportProgressChanged((int)progression);
-            babylonMaterialsToExport = new List<BabylonMaterial>();
+            progressionStep = 40.0f / babylonRootNodes.Count;
             babylonRootNodes.ForEach(babylonNode =>
             {
                 exportNodeRec(babylonNode, gltf, babylonScene);
@@ -60,15 +75,17 @@ namespace Max2Babylon
 
             // TODO - Choose between this method and the reverse of X axis
             // Switch from left to right handed coordinate system
+            RaiseMessage("GLTFExporter | Exporting root node");
             var tmpNodesList = new List<int>(scene.NodesList);
             var rootNode = new BabylonMesh
             {
                 name = "root",
                 rotation = new float[] { 0, (float)Math.PI, 0 },
-                scaling = new float[] { 1, 1, -1 }
+                scaling = new float[] { 1, 1, -1 },
+                idGroupInstance = -1
             };
             scene.NodesList.Clear();
-            gltfRootNode = ExportMesh(rootNode as BabylonMesh, gltf, null, babylonScene);
+            GLTFNode gltfRootNode = ExportAbstractMesh(rootNode, gltf, null);
             gltfRootNode.ChildrenList.AddRange(tmpNodesList);
 
             // Materials
@@ -108,19 +125,56 @@ namespace Max2Babylon
             ReportProgressChanged(100);
         }
 
+        private List<BabylonNode> initBabylonNodes(BabylonScene babylonScene)
+        {
+            babylonNodes = new List<BabylonNode>();
+            if (babylonScene.meshes != null)
+            {
+                int idGroupInstance = 0;
+                foreach (var babylonMesh in babylonScene.meshes)
+                {
+                    var babylonAbstractMeshes = new List<BabylonAbstractMesh>();
+                    babylonAbstractMeshes.Add(babylonMesh);
+                    if (babylonMesh.instances != null)
+                    {
+                        babylonAbstractMeshes.AddRange(babylonMesh.instances);
+                    }
+
+                    // Add mesh and instances to node list
+                    babylonNodes.AddRange(babylonAbstractMeshes);
+
+                    // Tag mesh and instances with an identifier
+                    babylonAbstractMeshes.ForEach(babylonAbstractMesh => babylonAbstractMesh.idGroupInstance = idGroupInstance);
+
+                    idGroupInstance++;
+                }
+            }
+            if (babylonScene.lights != null)
+            {
+                babylonNodes.AddRange(babylonScene.lights);
+            }
+            if (babylonScene.cameras != null)
+            {
+                babylonNodes.AddRange(babylonScene.cameras);
+            }
+            return babylonNodes;
+        }
+
         private void exportNodeRec(BabylonNode babylonNode, GLTF gltf, BabylonScene babylonScene, GLTFNode gltfParentNode = null)
         {
-            GLTFNode gltfNode = null; 
-            if (babylonNode.GetType() == typeof(BabylonMesh))
+            GLTFNode gltfNode = null;
+            var type = babylonNode.GetType();
+            if (type == typeof(BabylonAbstractMesh) ||
+                type.IsSubclassOf(typeof(BabylonAbstractMesh)))
             {
-                gltfNode = ExportMesh(babylonNode as BabylonMesh, gltf, gltfParentNode, babylonScene);
+                gltfNode = ExportAbstractMesh(babylonNode as BabylonAbstractMesh, gltf, gltfParentNode);
             }
-            else if (babylonNode.GetType() == typeof(BabylonCamera))
+            else if (type == typeof(BabylonCamera))
             {
                 GLTFCamera gltfCamera = ExportCamera(babylonNode as BabylonCamera, gltf, gltfParentNode);
                 gltfNode = gltfCamera.gltfNode;
             }
-            else if (babylonNode.GetType() == typeof(BabylonLight))
+            else if (type == typeof(BabylonLight))
             {
                 if (isNodeRelevantToExport(babylonNode, babylonScene))
                 {
@@ -140,7 +194,7 @@ namespace Max2Babylon
 
             CheckCancelled();
 
-            // If parent is exported successfully...
+            // If node is exported successfully...
             if (gltfNode != null)
             {
                 // ...export its children
@@ -149,27 +203,8 @@ namespace Max2Babylon
             }
         }
 
-        private List<BabylonNode> getNodes(BabylonScene babylonScene)
-        {
-            List<BabylonNode> babylonNodes = new List<BabylonNode>();
-            if (babylonScene.meshes != null)
-            {
-                babylonNodes.AddRange(babylonScene.meshes);
-            }
-            if (babylonScene.lights != null)
-            {
-                babylonNodes.AddRange(babylonScene.lights);
-            }
-            if (babylonScene.cameras != null)
-            {
-                babylonNodes.AddRange(babylonScene.cameras);
-            }
-            return babylonNodes;
-        }
-
         private List<BabylonNode> getDescendants(BabylonNode babylonNode, BabylonScene babylonScene)
         {
-            List<BabylonNode> babylonNodes = getNodes(babylonScene);
             return babylonNodes.FindAll(node => node.parentId == babylonNode.id);
         }
 
@@ -179,7 +214,8 @@ namespace Max2Babylon
         private bool isNodeRelevantToExport(BabylonNode babylonNode, BabylonScene babylonScene)
         {
             var type = babylonNode.GetType();
-            if (type == typeof(BabylonMesh) ||
+            if (type == typeof(BabylonAbstractMesh) ||
+                type.IsSubclassOf(typeof(BabylonAbstractMesh)) ||
                 type == typeof(BabylonCamera))
             {
                 return true;

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

@@ -194,6 +194,7 @@
     <Compile Include="Exporter\BabylonExporter.GLTFExporter.Light.cs" />
     <Compile Include="Exporter\BabylonExporter.GLTFExporter.Material.cs" />
     <Compile Include="Exporter\BabylonExporter.GLTFExporter.Camera.cs" />
+    <Compile Include="Exporter\BabylonExporter.GLTFExporter.AbstractMesh.cs" />
     <Compile Include="Exporter\BabylonExporter.GLTFExporter.Texture.cs" />
     <Compile Include="Exporter\BabylonExporter.GLTFExporter.Mesh.cs" />
     <Compile Include="Exporter\BabylonExporter.GLTFExporter.cs" />

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

@@ -7,12 +7,23 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private void ExportCamera(IIGameScene scene,  IIGameNode cameraNode, BabylonScene babylonScene)
+        private bool IsCameraExportable(IIGameNode cameraNode)
         {
             if (cameraNode.MaxNode.GetBoolProperty("babylonjs_noexport"))
             {
-                return;
+                return false;
             }
+
+            return true;
+        }
+
+        private BabylonCamera ExportCamera(IIGameScene scene,  IIGameNode cameraNode, BabylonScene babylonScene)
+        {
+            if (IsCameraExportable(cameraNode) == false)
+            {
+                return null;
+            }
+
             var gameCamera = cameraNode.IGameObject.AsGameCamera();
             var maxCamera = gameCamera.MaxObject as ICameraObject;
             var initialized = gameCamera.InitializeData;
@@ -23,7 +34,7 @@ namespace Max2Babylon
             babylonCamera.id = cameraNode.MaxNode.GetGuid().ToString();
             if (cameraNode.NodeParent != null)
             {
-                babylonCamera.parentId = GetParentID(cameraNode.NodeParent, babylonScene, scene);
+                babylonCamera.parentId = cameraNode.NodeParent.MaxNode.GetGuid().ToString();
             }
 
             babylonCamera.fov = Tools.ConvertFov(maxCamera.GetFOV(0, Tools.Forever));
@@ -66,17 +77,17 @@ namespace Max2Babylon
 
             var position = localTM.Translation;
             var rotation = localTM.Rotation;
-            var exportQuaternions = Loader.Core.RootNode.GetBoolProperty("babylonjs_exportquaternions");
 
             babylonCamera.position = new[] { position.X, position.Y, position.Z };
 
-            if (exportQuaternions)
+            var rotationQuaternion = new BabylonQuaternion { X = rotation.X, Y = rotation.Y, Z = rotation.Z, W = -rotation.W };
+            if (ExportQuaternionsInsteadOfEulers)
             {
-                babylonCamera.rotationQuaternion = new[] { rotation.X, rotation.Y, rotation.Z, -rotation.W };
+                babylonCamera.rotationQuaternion = rotationQuaternion.ToArray();
             }
             else
             {
-                babylonCamera.rotation = QuaternionToEulerAngles(rotation);
+                babylonCamera.rotation = rotationQuaternion.toEulerAngles().ToArray();
             }
 
             // Target
@@ -135,6 +146,8 @@ namespace Max2Babylon
             }
 
             babylonScene.CamerasList.Add(babylonCamera);
+
+            return babylonCamera;
         }
     }
 }

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

@@ -25,11 +25,21 @@ namespace Max2Babylon
             babylonScene.LightsList.Add(babylonLight);
         }
 
-        private void ExportLight(IIGameScene scene, IIGameNode lightNode, BabylonScene babylonScene)
+        private bool IsLightExportable(IIGameNode lightNode)
         {
             if (lightNode.MaxNode.GetBoolProperty("babylonjs_noexport"))
             {
-                return;
+                return false;
+            }
+
+            return true;
+        }
+
+        private BabylonLight ExportLight(IIGameScene scene, IIGameNode lightNode, BabylonScene babylonScene)
+        {
+            if (IsLightExportable(lightNode) == false)
+            {
+                return null;
             }
 
             var gameLight = lightNode.IGameObject.AsGameLight();
@@ -41,7 +51,7 @@ namespace Max2Babylon
             babylonLight.id = lightNode.MaxNode.GetGuid().ToString();
             if (lightNode.NodeParent != null)
             {
-                babylonLight.parentId = GetParentID(lightNode.NodeParent, babylonScene, scene);
+                babylonLight.parentId = lightNode.NodeParent.MaxNode.GetGuid().ToString();
             }
 
             // Type
@@ -222,6 +232,8 @@ namespace Max2Babylon
             }
 
             babylonScene.LightsList.Add(babylonLight);
+            
+            return babylonLight;
         }
     }
 }

+ 234 - 250
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Mesh.cs

@@ -1,128 +1,129 @@
-using System;
+using Autodesk.Max;
+using BabylonExport.Entities;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using Autodesk.Max;
-using BabylonExport.Entities;
-using System.Runtime.InteropServices;
 
 namespace Max2Babylon
 {
     partial class BabylonExporter
     {
+        private int bonesCount;
+
         readonly Dictionary<IIGameSkin, List<int>> skinSortedBones = new Dictionary<IIGameSkin, List<int>>();
-        List<int> SortBones(IIGameSkin skin)
+        
+        private bool IsMeshExportable(IIGameNode meshNode)
         {
-            var boneIds = new List<int>();
-            var boneIndex = new Dictionary<int, IIGameNode>();
-            for (var index = 0; index < skin.TotalSkinBoneCount; index++)
+            if (meshNode.MaxNode.GetBoolProperty("babylonjs_noexport"))
             {
-                var bone = skin.GetIGameBone(index, false);
-                if (bone == null)
-                {
-                    // non bone in skeletton
-                    boneIds.Add(-2);
-
-                }
-                else
-                {
-                    boneIds.Add(bone.NodeID);
-                    boneIndex[bone.NodeID] = bone;
-                }
+                return false;
             }
-            while (true)
+
+            if (!ExportHiddenObjects && meshNode.MaxNode.IsHidden(NodeHideFlags.None, false))
             {
-                bool foundMisMatch = false;
-                for (int i = 0; i < boneIds.Count; ++i)
-                {
-                    var id = boneIds[i];
-                    if (id == -2)
-                    {
-                        continue;
-                    }
-                    var parent = boneIndex[id].NodeParent;
-                    if (parent != null)
-                    {
-                        var parentId = parent.NodeID;
-                        if (boneIds.IndexOf(parentId) > i)
-                        {
-                            boneIds.RemoveAt(i);
-                            boneIds.Insert(boneIds.IndexOf(parentId) + 1, id);
-                            foundMisMatch = true;
-                            break;
-                        }
-                    }
-                }
-                if (!foundMisMatch)
-                {
-                    break;
-                }
+                return false;
             }
-            return boneIds;
 
+            return true;
         }
 
-        private string GetParentID(IIGameNode parentNode, BabylonScene babylonScene, IIGameScene scene)
+        private BabylonNode ExportDummy(IIGameScene scene, IIGameNode meshNode, BabylonScene babylonScene)
         {
-            var parentType = parentNode.IGameObject.IGameType;
-            var parentId = parentNode.MaxNode.GetGuid().ToString();
-            switch (parentType)
-            {
-                case Autodesk.Max.IGameObject.ObjectTypes.Light:
-                case Autodesk.Max.IGameObject.ObjectTypes.Mesh:
-                case Autodesk.Max.IGameObject.ObjectTypes.Camera:
-                    break;
+            RaiseMessage(meshNode.Name, 1);
 
+            var gameMesh = meshNode.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
 
-                default:
-                    if (babylonScene.MeshesList.All(m => m.id != parentId))
-                    {
-                        ExportMesh(scene, parentNode, babylonScene);
-                    }
-                    break;
-            }
-            return parentId;
-        }
+            var babylonMesh = new BabylonMesh { name = meshNode.Name, id = meshNode.MaxNode.GetGuid().ToString() };
+            babylonMesh.isDummy = true;
 
-        private float[] QuaternionToEulerAngles(IQuat rotation)
-        {
-            float rotx = 0, roty = 0, rotz = 0;
-            unsafe
-            {
-                rotation.GetEuler(new IntPtr(&rotx), new IntPtr(&roty), new IntPtr(&rotz));
-            }
-            return new[] { rotx, roty, rotz };
+            // Position / rotation / scaling / hierarchy
+            exportNode(babylonMesh, meshNode, scene, babylonScene);
+
+            babylonScene.MeshesList.Add(babylonMesh);
+
+            return babylonMesh;
         }
 
-        private int bonesCount;
-        private void ExportMesh(IIGameScene scene, IIGameNode meshNode, BabylonScene babylonScene)
+        private BabylonNode ExportMesh(IIGameScene scene, IIGameNode meshNode, BabylonScene babylonScene)
         {
-            if (meshNode.MaxNode.IsInstance())
+            if (IsMeshExportable(meshNode) == false)
             {
-                return;
+                return null;
             }
 
-            if (meshNode.MaxNode.GetBoolProperty("babylonjs_noexport"))
-            {
-                return;
-            }
+            RaiseMessage(meshNode.Name, 1);
 
-            if (!ExportHiddenObjects && meshNode.MaxNode.IsHidden(NodeHideFlags.None, false))
+            // Instances
+            var tabs = Loader.Global.NodeTab.Create();
+            Loader.Global.IInstanceMgr.InstanceMgr.GetInstances(meshNode.MaxNode, tabs);
+            if (tabs.Count > 1)
             {
-                return;
-            }
+                // For a mesh with instances, we distinguish between master and instance meshes:
+                //      - a master mesh stores all the info of the mesh (transform, hierarchy, animations + vertices, indices, materials, bones...)
+                //      - an instance mesh only stores the info of the node (transform, hierarchy, animations)
 
-            var gameMesh = meshNode.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
+                // Check if this mesh has already been exported
+                BabylonMesh babylonMasterMesh = null;
+                var index = 0;
+                while (babylonMasterMesh == null &&
+                       index < tabs.Count)
+                {
+#if MAX2017
+                    var indexer = index;
+#else
+                    var indexer = new IntPtr(index);
+#endif
+                    var tab = tabs[indexer];
 
-            var babylonMesh = new BabylonMesh { name = meshNode.Name, id = meshNode.MaxNode.GetGuid().ToString() };
+                    babylonMasterMesh = babylonScene.MeshesList.Find(_babylonMesh => {
+                               // Same id
+                        return _babylonMesh.id == tab.GetGuid().ToString() &&
+                               // Mesh is not a dummy
+                               _babylonMesh.isDummy == false;
+                    });
 
-            if (meshNode.NodeParent != null)
-            {
-                babylonMesh.parentId = GetParentID(meshNode.NodeParent, babylonScene, scene);
+                    index++;
+                }
+
+                if (babylonMasterMesh != null)
+                {
+                    // Mesh already exported
+                    // Export this node as instance
+
+                    meshNode.MaxNode.MarkAsInstance();
+                    
+                    var babylonInstanceMesh = new BabylonAbstractMesh { name = meshNode.Name, id = meshNode.MaxNode.GetGuid().ToString() };
+
+                    // Add instance to master mesh
+                    List<BabylonAbstractMesh> list = babylonMasterMesh.instances != null ? babylonMasterMesh.instances.ToList() : new List<BabylonAbstractMesh>();
+                    list.Add(babylonInstanceMesh);
+                    babylonMasterMesh.instances = list.ToArray();
+
+                    // Export transform / hierarchy / animations
+                    exportNode(babylonInstanceMesh, meshNode, scene, babylonScene);
+                    
+                    // Animations
+                    exportAnimation(babylonInstanceMesh, meshNode);
+
+                    return babylonInstanceMesh;
+                }
             }
+            
+            var gameMesh = meshNode.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 babylonMesh = new BabylonMesh { name = meshNode.Name, id = meshNode.MaxNode.GetGuid().ToString() };
+            
+            // Position / rotation / scaling / hierarchy
+            exportNode(babylonMesh, meshNode, scene, babylonScene);
 
+            // Animations
+            exportAnimation(babylonMesh, meshNode);
+            
             // Sounds
             var soundName = meshNode.MaxNode.GetStringProperty("babylonjs_sound_filename", "");
             if (!string.IsNullOrEmpty(soundName))
@@ -205,34 +206,7 @@ namespace Max2Babylon
                 skinSortedBones[skin] = boneIds;
             }
 
-            // Position / rotation / scaling
-            var localTM = meshNode.GetObjectTM(0);
-            if (meshNode.NodeParent != null)
-            {
-                var parentWorld = meshNode.NodeParent.GetObjectTM(0);
-                localTM.MultiplyBy(parentWorld.Inverse);
-            }
-
-            var meshTrans = localTM.Translation;
-            var meshRotation = localTM.Rotation;
-            var meshScale = localTM.Scaling;
-            var exportQuaternions = Loader.Core.RootNode.GetBoolProperty("babylonjs_exportquaternions");
-
-            babylonMesh.position = new[] { meshTrans.X, meshTrans.Y, meshTrans.Z };
-
-            if (exportQuaternions)
-            {
-                babylonMesh.rotationQuaternion = new[] { meshRotation.X, meshRotation.Y, meshRotation.Z, -meshRotation.W };
-            }
-            else
-            {
-                babylonMesh.rotation = QuaternionToEulerAngles(meshRotation);
-            }
-
-            babylonMesh.scaling = new[] { meshScale.X, meshScale.Y, meshScale.Z };
-
             // Mesh
-            RaiseMessage(meshNode.Name, 1);
 
             if (unskinnedMesh.IGameType == Autodesk.Max.IGameObject.ObjectTypes.Mesh && unskinnedMesh.MaxMesh != null)
             {
@@ -437,96 +411,11 @@ namespace Max2Babylon
 
                 // Buffers - Indices
                 babylonMesh.indices = indices.ToArray();
-
-            }
-
-            // Instances
-            var tabs = Loader.Global.NodeTab.Create();
-
-            Loader.Global.IInstanceMgr.InstanceMgr.GetInstances(meshNode.MaxNode, tabs);
-            var instances = new List<BabylonAbstractMesh>();
-
-            for (var index = 0; index < tabs.Count; index++)
-            {
-#if MAX2017
-                var indexer = index;
-#else
-                var indexer = new IntPtr(index);
-#endif
-                var tab = tabs[indexer];
-
-#if !MAX2017
-                Marshal.FreeHGlobal(indexer);
-#endif
-
-                if (meshNode.MaxNode.GetGuid() == tab.GetGuid())
-                {
-                    continue;
-                }
-                var instanceGameNode = scene.GetIGameNode(tab);
-                if (instanceGameNode == null)
-                {
-                    continue;
-                }
-                tab.MarkAsInstance();
-
-                var instance = new BabylonAbstractMesh { name = tab.Name };
-                {
-                    var instanceLocalTM = instanceGameNode.GetObjectTM(0);
-
-                    var instanceTrans = instanceLocalTM.Translation;
-                    var instanceRotation = instanceLocalTM.Rotation;
-                    var instanceScale = instanceLocalTM.Scaling;
-
-                    instance.id = instanceGameNode.MaxNode.GetGuid().ToString();
-                    instance.position = new[] { instanceTrans.X, instanceTrans.Y, instanceTrans.Z };
-
-                    if (exportQuaternions)
-                    {
-                        instance.rotationQuaternion = new[] { instanceRotation.X, instanceRotation.Y, instanceRotation.Z, -instanceRotation.W };
-                    }
-                    else
-                    {
-                        instance.rotation = QuaternionToEulerAngles(instanceRotation);
-                    }
-
-                    instance.scaling = new[] { instanceScale.X, instanceScale.Y, instanceScale.Z };
-
-                    if (instanceGameNode.NodeParent != null)
-                    {
-                        instance.parentId = GetParentID(instanceGameNode.NodeParent, babylonScene, scene);
-                    }
-                }
-                var instanceAnimations = new List<BabylonAnimation>();
-                GenerateCoordinatesAnimations(meshNode, instanceAnimations);
-                instance.animations = instanceAnimations.ToArray();
-
-                instances.Add(instance);
-            }
-
-            babylonMesh.instances = instances.ToArray();
-
-            // Animations
-            var animations = new List<BabylonAnimation>();
-
-            GenerateCoordinatesAnimations(meshNode, animations);
-
-            if (!ExportFloatController(meshNode.MaxNode.VisController, "visibility", animations))
-            {
-                ExportFloatAnimation("visibility", animations, key => new[] { meshNode.MaxNode.GetVisibility(key, Tools.Forever) });
-            }
-
-            babylonMesh.animations = animations.ToArray();
-
-            if (meshNode.MaxNode.GetBoolProperty("babylonjs_autoanimate", 1))
-            {
-                babylonMesh.autoAnimate = true;
-                babylonMesh.autoAnimateFrom = (int)meshNode.MaxNode.GetFloatProperty("babylonjs_autoanimate_from");
-                babylonMesh.autoAnimateTo = (int)meshNode.MaxNode.GetFloatProperty("babylonjs_autoanimate_to", 100);
-                babylonMesh.autoAnimateLoop = meshNode.MaxNode.GetBoolProperty("babylonjs_autoanimateloop", 1);
             }
 
             babylonScene.MeshesList.Add(babylonMesh);
+
+            return babylonMesh;
         }
 
         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)
@@ -573,63 +462,56 @@ namespace Max2Babylon
             CheckCancelled();
         }
 
-        public static void GenerateCoordinatesAnimations(IIGameNode meshNode, List<BabylonAnimation> animations)
+        List<int> SortBones(IIGameSkin skin)
         {
-            if (meshNode.IGameControl.IsAnimated(IGameControlType.Pos) ||
-                meshNode.IGameControl.IsAnimated(IGameControlType.PosX) ||
-                meshNode.IGameControl.IsAnimated(IGameControlType.PosY) ||
-                meshNode.IGameControl.IsAnimated(IGameControlType.PosZ))
+            var boneIds = new List<int>();
+            var boneIndex = new Dictionary<int, IIGameNode>();
+            for (var index = 0; index < skin.TotalSkinBoneCount; index++)
             {
-                ExportVector3Animation("position", animations, key =>
+                var bone = skin.GetIGameBone(index, false);
+                if (bone == null)
                 {
-                    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 };
-                });
-            }
+                    // non bone in skeletton
+                    boneIds.Add(-2);
 
-            if (meshNode.IGameControl.IsAnimated(IGameControlType.Rot) ||
-                meshNode.IGameControl.IsAnimated(IGameControlType.EulerX) ||
-                meshNode.IGameControl.IsAnimated(IGameControlType.EulerY) ||
-                meshNode.IGameControl.IsAnimated(IGameControlType.EulerZ))
-            {
-                ExportQuaternionAnimation("rotationQuaternion", animations, key =>
+                }
+                else
                 {
-                    var worldMatrix = meshNode.GetObjectTM(key);
-                    if (meshNode.NodeParent != null)
-                    {
-                        var parentWorld = meshNode.NodeParent.GetObjectTM(key);
-                        worldMatrix.MultiplyBy(parentWorld.Inverse);
-                    }
-
-
-                    var rot = worldMatrix.Rotation;
-                    return new[] { rot.X, rot.Y, rot.Z, -rot.W };
-                });
+                    boneIds.Add(bone.NodeID);
+                    boneIndex[bone.NodeID] = bone;
+                }
             }
-
-            if (meshNode.IGameControl.IsAnimated(IGameControlType.Scale))
+            while (true)
             {
-                ExportVector3Animation("scaling", animations, key =>
+                bool foundMisMatch = false;
+                for (int i = 0; i < boneIds.Count; ++i)
                 {
-                    var worldMatrix = meshNode.GetObjectTM(key);
-                    if (meshNode.NodeParent != null)
+                    var id = boneIds[i];
+                    if (id == -2)
                     {
-                        var parentWorld = meshNode.NodeParent.GetObjectTM(key);
-                        worldMatrix.MultiplyBy(parentWorld.Inverse);
+                        continue;
                     }
-                    var scale = worldMatrix.Scaling;
-
-                    return new[] { scale.X, scale.Y, scale.Z };
-                });
+                    var parent = boneIndex[id].NodeParent;
+                    if (parent != null)
+                    {
+                        var parentId = parent.NodeID;
+                        if (boneIds.IndexOf(parentId) > i)
+                        {
+                            boneIds.RemoveAt(i);
+                            boneIds.Insert(boneIds.IndexOf(parentId) + 1, id);
+                            foundMisMatch = true;
+                            break;
+                        }
+                    }
+                }
+                if (!foundMisMatch)
+                {
+                    break;
+                }
             }
-        }
+            return boneIds;
 
+        }
 
         int CreateGlobalVertex(IIGameMesh mesh, IFaceEx face, int facePart, List<GlobalVertex> vertices, bool hasUV, bool hasUV2, bool hasColor, bool hasAlpha, List<GlobalVertex>[] verticesAlreadyExported, IIGameSkin skin, List<int> boneIds)
         {
@@ -800,5 +682,107 @@ namespace Max2Babylon
 
             return vertices.Count - 1;
         }
+
+        private void exportNode(BabylonAbstractMesh babylonAbstractMesh, IIGameNode maxGameNode, IIGameScene maxGameScene, BabylonScene babylonScene)
+        {
+            // Position / rotation / scaling
+            exportTransform(babylonAbstractMesh, maxGameNode);
+            
+            // Hierarchy
+            if (maxGameNode.NodeParent != null)
+            {
+                babylonAbstractMesh.parentId = maxGameNode.NodeParent.MaxNode.GetGuid().ToString();
+            }
+        }
+
+        private void exportTransform(BabylonAbstractMesh babylonAbstractMesh, IIGameNode maxGameNode)
+        {
+            // Position / rotation / scaling
+            var localTM = maxGameNode.GetObjectTM(0);
+            if (maxGameNode.NodeParent != null)
+            {
+                var parentWorld = maxGameNode.NodeParent.GetObjectTM(0);
+                localTM.MultiplyBy(parentWorld.Inverse);
+            }
+
+            var meshTrans = localTM.Translation;
+            var meshRotation = localTM.Rotation;
+            var meshScale = localTM.Scaling;
+            
+            babylonAbstractMesh.position = new[] { meshTrans.X, meshTrans.Y, meshTrans.Z };
+
+            var rotationQuaternion = new BabylonQuaternion { X = meshRotation.X, Y = meshRotation.Y, Z = meshRotation.Z, W = -meshRotation.W };
+            if (ExportQuaternionsInsteadOfEulers)
+            {
+                babylonAbstractMesh.rotationQuaternion = rotationQuaternion.ToArray();
+            }
+            else
+            {
+                babylonAbstractMesh.rotation = rotationQuaternion.toEulerAngles().ToArray();
+            }
+
+            babylonAbstractMesh.scaling = new[] { meshScale.X, meshScale.Y, meshScale.Z };
+        }
+
+        private void exportAnimation(BabylonNode babylonNode, IIGameNode maxGameNode)
+        {
+            var animations = new List<BabylonAnimation>();
+
+            GenerateCoordinatesAnimations(maxGameNode, animations);
+
+            if (!ExportFloatController(maxGameNode.MaxNode.VisController, "visibility", animations))
+            {
+                ExportFloatAnimation("visibility", animations, key => new[] { maxGameNode.MaxNode.GetVisibility(key, Tools.Forever) });
+            }
+
+            babylonNode.animations = animations.ToArray();
+
+            if (maxGameNode.MaxNode.GetBoolProperty("babylonjs_autoanimate", 1))
+            {
+                babylonNode.autoAnimate = true;
+                babylonNode.autoAnimateFrom = (int)maxGameNode.MaxNode.GetFloatProperty("babylonjs_autoanimate_from");
+                babylonNode.autoAnimateTo = (int)maxGameNode.MaxNode.GetFloatProperty("babylonjs_autoanimate_to", 100);
+                babylonNode.autoAnimateLoop = maxGameNode.MaxNode.GetBoolProperty("babylonjs_autoanimateloop", 1);
+            }
+        }
+
+        public void GenerateCoordinatesAnimations(IIGameNode meshNode, List<BabylonAnimation> animations)
+        {
+            ExportVector3Animation("position", animations, key =>
+            {
+                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 =>
+            {
+                var worldMatrix = meshNode.GetObjectTM(key);
+                if (meshNode.NodeParent != null)
+                {
+                    var parentWorld = meshNode.NodeParent.GetObjectTM(key);
+                    worldMatrix.MultiplyBy(parentWorld.Inverse);
+                }
+                var rot = worldMatrix.Rotation;
+                return new[] { rot.X, rot.Y, rot.Z, -rot.W };
+            });
+
+            ExportVector3Animation("scaling", animations, key =>
+            {
+                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 };
+            });
+        }
     }
 }

+ 211 - 95
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.cs

@@ -2,6 +2,7 @@
 using BabylonExport.Entities;
 using Newtonsoft.Json;
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Globalization;
 using System.IO;
@@ -158,44 +159,69 @@ namespace Max2Babylon
                 }
             }
 
-            // Cameras
-            BabylonCamera mainCamera = null;
-            ICameraObject mainCameraNode = null;
-
-            RaiseMessage("Exporting cameras");
-            var camerasTab = gameScene.GetIGameNodeByType(Autodesk.Max.IGameObject.ObjectTypes.Camera);
-            for (int ix = 0; ix < camerasTab.Count; ++ix)
+            // Root nodes
+            RaiseMessage("Exporting nodes");
+            HashSet<IIGameNode> maxRootNodes = getRootNodes(gameScene);
+            var progressionStep = 80.0f / maxRootNodes.Count;
+            var progression = 10.0f;
+            ReportProgressChanged((int)progression);
+            referencedMaterials.Clear();
+            foreach (var maxRootNode in maxRootNodes)
             {
-#if MAX2017
-                var indexer = ix;
-#else
-                var indexer = new IntPtr(ix);
-#endif
-                var cameraNode = camerasTab[indexer];
-
-#if !MAX2017
-                Marshal.FreeHGlobal(indexer);
-#endif
-                
-                ExportCamera(gameScene, cameraNode, babylonScene);
+                exportNodeRec(maxRootNode, babylonScene, gameScene);
+                progression += progressionStep;
+                ReportProgressChanged((int)progression);
+                CheckCancelled();
+            };
+            RaiseMessage(string.Format("Total meshes: {0}", babylonScene.MeshesList.Count), Color.Gray, 1);
 
-                if (mainCamera == null && babylonScene.CamerasList.Count > 0)
-                {
-                    mainCameraNode = (cameraNode.MaxNode.ObjectRef as ICameraObject);
-                    mainCamera = babylonScene.CamerasList[0];
-                    babylonScene.activeCameraID = mainCamera.id;
-                    RaiseMessage("Active camera set to " + mainCamera.name, Color.Green, 1, true);
-                }
+            // Main camera
+            BabylonCamera babylonMainCamera = null;
+            ICameraObject maxMainCameraObject = null;
+            if (babylonMainCamera == null && babylonScene.CamerasList.Count > 0)
+            {
+                // Set first camera as main one
+                babylonMainCamera = babylonScene.CamerasList[0];
+                babylonScene.activeCameraID = babylonMainCamera.id;
+                RaiseMessage("Active camera set to " + babylonMainCamera.name, Color.Green, 1, true);
+
+                // Retreive camera node with same GUID
+                var maxCameraNodesAsTab = gameScene.GetIGameNodeByType(Autodesk.Max.IGameObject.ObjectTypes.Camera);
+                var maxCameraNodes = TabToList(maxCameraNodesAsTab);
+                var maxMainCameraNode = maxCameraNodes.Find(_camera => _camera.MaxNode.GetGuid().ToString() == babylonMainCamera.id);
+                maxMainCameraObject = (maxMainCameraNode.MaxNode.ObjectRef as ICameraObject);
             }
 
-            if (mainCamera == null)
+            if (babylonMainCamera == null)
             {
                 RaiseWarning("No camera defined", 1);
             }
             else
             {
-                RaiseMessage(string.Format("Total: {0}", babylonScene.CamerasList.Count), Color.Gray, 1);
+                RaiseMessage(string.Format("Total cameras: {0}", babylonScene.CamerasList.Count), Color.Gray, 1);
+            }
+
+            // Default light
+            if (babylonScene.LightsList.Count == 0)
+            {
+                RaiseWarning("No light defined", 1);
+                RaiseWarning("A default hemispheric light was added for your convenience", 1);
+                ExportDefaultLight(babylonScene);
+            }
+            else
+            {
+                RaiseMessage(string.Format("Total lights: {0}", babylonScene.LightsList.Count), Color.Gray, 1);
+            }
+
+            // Materials
+            RaiseMessage("Exporting materials");
+            var matsToExport = referencedMaterials.ToArray(); // Snapshot because multimaterials can export new materials
+            foreach (var mat in matsToExport)
+            {
+                ExportMaterial(mat, babylonScene);
+                CheckCancelled();
             }
+            RaiseMessage(string.Format("Total: {0}", babylonScene.MaterialsList.Count + babylonScene.MultiMaterialsList.Count), Color.Gray, 1);
 
             // Fog
             for (var index = 0; index < Loader.Core.NumAtmospheric; index++)
@@ -222,78 +248,14 @@ namespace Max2Babylon
                         babylonScene.fogMode = 3;
                     }
 #endif
-                    if (mainCamera != null)
+                    if (babylonMainCamera != null)
                     {
-                        babylonScene.fogStart = mainCameraNode.GetEnvRange(0, 0, Tools.Forever);
-                        babylonScene.fogEnd = mainCameraNode.GetEnvRange(0, 1, Tools.Forever);
+                        babylonScene.fogStart = maxMainCameraObject.GetEnvRange(0, 0, Tools.Forever);
+                        babylonScene.fogEnd = maxMainCameraObject.GetEnvRange(0, 1, Tools.Forever);
                     }
                 }
             }
 
-            // Meshes
-            ReportProgressChanged(10);
-            RaiseMessage("Exporting meshes");
-            var meshes = gameScene.GetIGameNodeByType(Autodesk.Max.IGameObject.ObjectTypes.Mesh);
-            var progressionStep = 80.0f / meshes.Count;
-            var progression = 10.0f;
-            for (int ix = 0; ix < meshes.Count; ++ix)
-            {
-#if MAX2017
-                var indexer = ix;
-#else
-                var indexer = new IntPtr(ix);
-#endif
-                var meshNode = meshes[indexer];
-
-#if !MAX2017
-                Marshal.FreeHGlobal(indexer);
-#endif
-                ExportMesh(gameScene, meshNode, babylonScene);
-
-
-                ReportProgressChanged((int)progression);
-
-                progression += progressionStep;
-
-                CheckCancelled();
-            }
-
-
-            // Materials
-            RaiseMessage("Exporting materials");
-            var matsToExport = referencedMaterials.ToArray(); // Snapshot because multimaterials can export new materials
-            foreach (var mat in matsToExport)
-            {
-                ExportMaterial(mat, babylonScene);
-                CheckCancelled();
-            }
-            RaiseMessage(string.Format("Total: {0}", babylonScene.MaterialsList.Count + babylonScene.MultiMaterialsList.Count), Color.Gray, 1);
-
-            // Lights
-            RaiseMessage("Exporting lights");
-            var lightNodes = gameScene.GetIGameNodeByType(Autodesk.Max.IGameObject.ObjectTypes.Light);
-            for (var i = 0; i < lightNodes.Count; ++i)
-            {
-#if MAX2017
-                ExportLight(gameScene, lightNodes[i], babylonScene);
-#else
-                    ExportLight(gameScene, lightNodes[new IntPtr(i)], babylonScene);
-#endif
-                CheckCancelled();
-            }
-
-
-            if (babylonScene.LightsList.Count == 0)
-            {
-                RaiseWarning("No light defined", 1);
-                RaiseWarning("A default hemispheric light was added for your convenience", 1);
-                ExportDefaultLight(babylonScene);
-            }
-            else
-            {
-                RaiseMessage(string.Format("Total: {0}", babylonScene.LightsList.Count), Color.Gray, 1);
-            }
-
             // Skeletons
             if (skins.Count > 0)
             {
@@ -350,5 +312,159 @@ namespace Max2Babylon
             watch.Stop();
             RaiseMessage(string.Format("Exportation done in {0:0.00}s", watch.ElapsedMilliseconds / 1000.0), Color.Blue);
         }
+
+        private void exportNodeRec(IIGameNode maxGameNode, BabylonScene babylonScene, IIGameScene maxGameScene)
+        {
+            BabylonNode babylonNode = null;
+            bool hasExporter = true;
+            switch (maxGameNode.IGameObject.IGameType)
+            {
+                case Autodesk.Max.IGameObject.ObjectTypes.Mesh:
+                    babylonNode = ExportMesh(maxGameScene, maxGameNode, babylonScene);
+                    break;
+                case Autodesk.Max.IGameObject.ObjectTypes.Camera:
+                    babylonNode = ExportCamera(maxGameScene, maxGameNode, babylonScene);
+                    break;
+                case Autodesk.Max.IGameObject.ObjectTypes.Light:
+                    babylonNode = ExportLight(maxGameScene, maxGameNode, babylonScene);
+                    break;
+                default:
+                    // The type of node is not exportable (helper, spline, xref...)
+                    hasExporter = false;
+                    break;
+            }
+            CheckCancelled();
+
+            // If node is not exported successfully but is significant
+            if (babylonNode == null &&
+                isNodeRelevantToExport(maxGameNode))
+            {
+                if (!hasExporter)
+                {
+                    RaiseWarning($"Type '{maxGameNode.IGameObject.IGameType}' of node '{maxGameNode.Name}' has no exporter, an empty node is exported instead", 1);
+                }
+                // Create a dummy (empty mesh)
+                babylonNode = ExportDummy(maxGameScene, maxGameNode, babylonScene);
+            };
+            
+            if (babylonNode != null)
+            {
+                // Export its children
+                for (int i = 0; i < maxGameNode.ChildCount; i++)
+                {
+                    var descendant = maxGameNode.GetNodeChild(i);
+                    exportNodeRec(descendant, babylonScene, maxGameScene);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Return true if node descendant hierarchy has any exportable Mesh, Camera or Light
+        /// </summary>
+        private bool isNodeRelevantToExport(IIGameNode maxGameNode)
+        {
+            bool isRelevantToExport;
+            switch (maxGameNode.IGameObject.IGameType)
+            {
+                case Autodesk.Max.IGameObject.ObjectTypes.Mesh:
+                    isRelevantToExport = IsMeshExportable(maxGameNode);
+                    break;
+                case Autodesk.Max.IGameObject.ObjectTypes.Camera:
+                    isRelevantToExport = IsCameraExportable(maxGameNode);
+                    break;
+                case Autodesk.Max.IGameObject.ObjectTypes.Light:
+                    isRelevantToExport = IsLightExportable(maxGameNode);
+                    break;
+                default:
+                    isRelevantToExport = false;
+                    break;
+            }
+
+            if (isRelevantToExport)
+            {
+                return true;
+            }
+
+            // Descandant recursivity
+            List<IIGameNode> maxDescendants = getDescendants(maxGameNode);
+            int indexDescendant = 0;
+            while (indexDescendant < maxDescendants.Count) // while instead of for to stop as soon as a relevant node has been found
+            {
+                if (isNodeRelevantToExport(maxDescendants[indexDescendant]))
+                {
+                    return true;
+                }
+                indexDescendant++;
+            }
+
+            // No relevant node found in hierarchy
+            return false;
+        }
+
+        private List<IIGameNode> getDescendants(IIGameNode maxGameNode)
+        {
+            var maxDescendants = new List<IIGameNode>();
+            for (int i = 0; i < maxGameNode.ChildCount; i++)
+            {
+                maxDescendants.Add(maxGameNode.GetNodeChild(i));
+            }
+            return maxDescendants;
+        }
+
+        private HashSet<IIGameNode> getRootNodes(IIGameScene maxGameScene)
+        {
+            HashSet<IIGameNode> maxGameNodes = new HashSet<IIGameNode>();
+
+            Func<IIGameNode, IIGameNode> getMaxRootNode = delegate (IIGameNode maxGameNode)
+            {
+                while (maxGameNode.NodeParent != null)
+                {
+                    maxGameNode = maxGameNode.NodeParent;
+                }
+                return maxGameNode;
+            };
+
+            Action<Autodesk.Max.IGameObject.ObjectTypes> addMaxRootNodes = delegate (Autodesk.Max.IGameObject.ObjectTypes type)
+            {
+                ITab<IIGameNode> maxGameNodesOfType = maxGameScene.GetIGameNodeByType(type);
+                if (maxGameNodesOfType != null)
+                {
+                    TabToList(maxGameNodesOfType).ForEach(maxGameNode =>
+                    {
+                        var maxRootNode = getMaxRootNode(maxGameNode);
+                        maxGameNodes.Add(maxRootNode);
+                    });
+                }
+            };
+
+            addMaxRootNodes(Autodesk.Max.IGameObject.ObjectTypes.Mesh);
+            addMaxRootNodes(Autodesk.Max.IGameObject.ObjectTypes.Light);
+            addMaxRootNodes(Autodesk.Max.IGameObject.ObjectTypes.Camera);
+
+            return maxGameNodes;
+        }
+
+        private static List<T> TabToList<T>(ITab<T> tab)
+        {
+            if (tab == null)
+            {
+                return null;
+            }
+            else
+            {
+                List<T> list = new List<T>();
+                for (int i = 0; i < tab.Count; i++)
+                {
+#if MAX2017
+                    var indexer = i;
+#else
+                    var indexer = new IntPtr(i);
+                    Marshal.FreeHGlobal(indexer);
+#endif
+                    list.Add(tab[indexer]);
+                }
+                return list;
+            }
+        }
     }
 }

+ 4 - 4
dist/preview release/what's new.md

@@ -6,10 +6,10 @@
 - Engine can now be initialized with an existing webgl context ([deltakosh](https://github.com/deltakosh))
 - Introduced behaviors. [Doc here](http://doc.babylonjs.com/overviews/behaviors) ([deltakosh](https://github.com/deltakosh))
 - Added support for WebGL Occlusion queries. [Doc here](http://doc.babylonjs.com/overviews/occlusionquery) ([Ibraheem Osama](https://github.com/IbraheemOsama))
-- New behaviors for ArcRotateCamera:
- - AutoRotation ([deltakosh](https://github.com/deltakosh))
- - Framing ([deltakosh](https://github.com/deltakosh))
- - Bouncing ([deltakosh](https://github.com/deltakosh))
+- New behaviors for ArcRotateCamera [Doc here](http://doc.babylonjs.com/overviews/behaviors): 
+  - AutoRotation ([deltakosh](https://github.com/deltakosh))
+  - Framing ([deltakosh](https://github.com/deltakosh))
+  - Bouncing ([deltakosh](https://github.com/deltakosh))
 - New InputText for Babylon.GUI. [Doc here](http://doc.babylonjs.com/overviews/gui#inputtext) ([deltakosh](https://github.com/deltakosh))
 - New VirtualKeyboard for Babylon.GUI. [Doc here](http://doc.babylonjs.com/overviews/gui#virtualkeyboard) ([deltakosh](https://github.com/deltakosh) / [adam](https://github.com/abow))
 - Added support for depth pre-pass rendering. [Doc here](http://doc.babylonjs.com/tutorials/transparency_and_how_meshes_are_rendered#depth-pre-pass-meshes) ([deltakosh](https://github.com/deltakosh))