فهرست منبع

Merge pull request #2789 from noalak/master

Exporter 3ds Max > PBR materials | glb file format
David Catuhe 8 سال پیش
والد
کامیت
9b23416e16

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

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

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

@@ -0,0 +1,76 @@
+using System.Runtime.Serialization;
+
+namespace BabylonExport.Entities
+{
+    [DataContract]
+    public class BabylonPBRMetallicRoughnessMaterial : BabylonMaterial
+    {
+        [DataMember]
+        public string customType { get; private set; }
+
+        [DataMember]
+        public float[] baseColor { get; set; }
+
+        [DataMember]
+        public BabylonTexture baseTexture { get; set; }
+
+        [DataMember]
+        public float metallic { get; set; }
+
+        [DataMember]
+        public float roughness { get; set; }
+
+        [DataMember]
+        public BabylonTexture metallicRoughnessTexture { get; set; }
+
+        [DataMember]
+        public int maxSimultaneousLights { get; set; }
+
+        [DataMember]
+        public bool disableLighting { get; set; }
+
+        [DataMember]
+        public BabylonTexture environmentTexture { get; set; }
+
+        [DataMember]
+        public bool invertNormalMapX { get; set; }
+
+        [DataMember]
+        public bool invertNormalMapY { get; set; }
+
+        [DataMember]
+        public BabylonTexture normalTexture { get; set; }
+
+        [DataMember]
+        public float[] emissiveColor { get; set; }
+
+        [DataMember]
+        public BabylonTexture emissiveTexture { get; set; }
+
+        [DataMember]
+        public float occlusionStrength { get; set; }
+
+        [DataMember]
+        public BabylonTexture occlusionTexture { get; set; }
+
+        [DataMember]
+        public float alphaCutOff { get; set; }
+
+        [DataMember]
+        public int transparencyMode { get; set; }
+
+        [DataMember]
+        public bool doubleSided { get; set; }
+
+        public BabylonPBRMetallicRoughnessMaterial() : base()
+        {
+            customType = "BABYLON.PBRMetallicRoughnessMaterial";
+
+            maxSimultaneousLights = 4;
+            emissiveColor = new[] { 0f, 0f, 0f };
+            occlusionStrength = 1.0f;
+            alphaCutOff = 0.4f;
+            transparencyMode = 0;
+        }
+    }
+}

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

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.IO;
 using System.Runtime.Serialization;
 
 namespace GLTFExport.Entities
@@ -45,7 +46,8 @@ namespace GLTFExport.Entities
         [DataMember(EmitDefaultValue = false)]
         public GLTFSampler[] samplers { get; set; }
 
-        public string OutputPath { get; private set; }
+        public string OutputFolder { get; private set; }
+        public string OutputFile { get; private set; }
 
         public List<GLTFNode> NodesList { get; private set; }
         public List<GLTFCamera> CamerasList { get; private set; }
@@ -58,9 +60,17 @@ namespace GLTFExport.Entities
         public List<GLTFImage> ImagesList { get; private set; }
         public List<GLTFSampler> SamplersList { get; private set; }
 
+        public GLTFBuffer buffer;
+        public GLTFBufferView bufferViewScalar;
+        public GLTFBufferView bufferViewFloatVec3;
+        public GLTFBufferView bufferViewFloatVec4;
+        public GLTFBufferView bufferViewFloatVec2;
+        public GLTFBufferView bufferViewImage;
+
         public GLTF(string outputPath)
         {
-            OutputPath = outputPath;
+            OutputFolder = Path.GetDirectoryName(outputPath);
+            OutputFile = Path.GetFileNameWithoutExtension(outputPath);
 
             NodesList = new List<GLTFNode>();
             CamerasList = new List<GLTFCamera>();

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

@@ -1,4 +1,5 @@
-using System.Runtime.Serialization;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
 
 namespace GLTFExport.Entities
 {
@@ -10,5 +11,7 @@ namespace GLTFExport.Entities
 
         [DataMember(IsRequired = true)]
         public int byteLength { get; set; }
+
+        public List<byte> bytesList;
     }
 }

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

@@ -1,4 +1,5 @@
-using System.Runtime.Serialization;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
 
 namespace GLTFExport.Entities
 {
@@ -18,5 +19,6 @@ namespace GLTFExport.Entities
         public int? byteStride { get; set; }
 
         public GLTFBuffer Buffer;
+        public List<byte> bytesList = new List<byte>();
     }
 }

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

@@ -13,5 +13,7 @@ namespace GLTFExport.Entities
 
         [DataMember(EmitDefaultValue = false)]
         public int? bufferView { get; set; }
+
+        public string FileExtension;
     }
 }

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


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


+ 177 - 22
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Material.cs

@@ -24,9 +24,9 @@ namespace Max2Babylon
 
                 RaiseMessage("GLTFExporter.Material | babylonMaterial data", 2);
                 RaiseMessage("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
+                RaiseMessage("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 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++)
@@ -128,7 +128,7 @@ namespace Max2Babylon
                 };
 
                 MetallicRoughness _metallicRoughness = ConvertToMetallicRoughness(_specularGlossiness, true);
-
+                
                 // Base color
                 gltfPbrMetallicRoughness.baseColorFactor = new float[4]
                 {
@@ -147,43 +147,40 @@ namespace Max2Babylon
 
                 if (babylonStandardMaterial.diffuseTexture != null)
                 {
-                    Func<string, Bitmap> loadTexture = delegate (string textureName)
+                    Func<string, Bitmap> loadTextureFromOutput = 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;
-                        }
+                        return LoadTexture(Path.Combine(gltf.OutputFolder, textureName));
                     };
 
-                    Bitmap diffuseBitmap = loadTexture(babylonStandardMaterial.diffuseTexture.name);
+                    Bitmap diffuseBitmap = loadTextureFromOutput(babylonStandardMaterial.diffuseTexture.name);
 
                     if (diffuseBitmap != null)
                     {
                         Bitmap specularBitmap = null;
                         if (babylonStandardMaterial.specularTexture != null)
                         {
-                            specularBitmap = loadTexture(babylonStandardMaterial.specularTexture.name);
+                            specularBitmap = loadTextureFromOutput(babylonStandardMaterial.specularTexture.name);
                         }
 
                         Bitmap opacityBitmap = null;
                         if (babylonStandardMaterial.diffuseTexture.hasAlpha == false && babylonStandardMaterial.opacityTexture != null)
                         {
-                            opacityBitmap = loadTexture(babylonStandardMaterial.opacityTexture.name);
+                            opacityBitmap = loadTextureFromOutput(babylonStandardMaterial.opacityTexture.name);
                         }
 
-                        // Retreive dimension from diffuse map
-                        var width = diffuseBitmap.Width;
-                        var height = diffuseBitmap.Height;
+                        // Retreive dimensions
+                        int width = 0;
+                        int height = 0;
+                        var haveSameDimensions = _getMinimalBitmapDimensions(out width, out height, diffuseBitmap, specularBitmap, opacityBitmap);
+                        if (!haveSameDimensions)
+                        {
+                            RaiseWarning("Diffuse, specular and opacity maps should have same dimensions", 2);
+                        }
 
                         // Create base color and metallic+roughness maps
                         Bitmap baseColorBitmap = new Bitmap(width, height);
                         Bitmap metallicRoughnessBitmap = new Bitmap(width, height);
+                        var hasAlpha = false;
                         for (int x = 0; x < width; x++)
                         {
                             for (int y = 0; y < height; y++)
@@ -199,7 +196,7 @@ namespace Max2Babylon
                                     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
+                                                 0
                                 };
 
                                 var displayPrints = x == width / 2 && y == height / 2;
@@ -212,6 +209,10 @@ namespace Max2Babylon
                                     (int)(metallicRoughnessTexture.baseColor.b * 255)
                                 );
                                 baseColorBitmap.SetPixel(x, y, colorBase);
+                                if (metallicRoughnessTexture.opacity != 1)
+                                {
+                                    hasAlpha = true;
+                                }
 
                                 // The metalness values are sampled from the B channel.
                                 // The roughness values are sampled from the G channel.
@@ -226,11 +227,150 @@ namespace Max2Babylon
                         }
 
                         // 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);
+                        var baseColorFileName = babylonMaterial.name + "_baseColor" + (hasAlpha ? ".png" : ".jpg");
+                        gltfPbrMetallicRoughness.baseColorTexture = ExportBitmapTexture(babylonStandardMaterial.diffuseTexture, baseColorBitmap, baseColorFileName, gltf);
+                        if (specularBitmap != null)
+                        {
+                            gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportBitmapTexture(babylonStandardMaterial.diffuseTexture, metallicRoughnessBitmap, babylonMaterial.name + "_metallicRoughness" + ".jpg", gltf);
+                        }
                     }
                 }
             }
+            else if (babylonMaterial.GetType() == typeof(BabylonPBRMetallicRoughnessMaterial))
+            {
+
+                var babylonPBRMetallicRoughnessMaterial = babylonMaterial as BabylonPBRMetallicRoughnessMaterial;
+
+
+                // --- prints ---
+
+                RaiseMessage("GLTFExporter.Material | babylonMaterial data", 2);
+                RaiseMessage("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3);
+                RaiseMessage("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 3);
+                RaiseMessage("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3);
+                RaiseMessage("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3);
+
+                // Global
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.maxSimultaneousLights=" + babylonPBRMetallicRoughnessMaterial.maxSimultaneousLights, 3);
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.disableLighting=" + babylonPBRMetallicRoughnessMaterial.disableLighting, 3);
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.alphaCutOff=" + babylonPBRMetallicRoughnessMaterial.alphaCutOff, 3);
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.transparencyMode=" + babylonPBRMetallicRoughnessMaterial.transparencyMode, 3);
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.doubleSided=" + babylonPBRMetallicRoughnessMaterial.doubleSided, 3);
+
+                // Base color
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseColor.Length=" + babylonPBRMetallicRoughnessMaterial.baseColor.Length, 3);
+                for (int i = 0; i < babylonPBRMetallicRoughnessMaterial.baseColor.Length; i++)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseColor[" + i + "]=" + babylonPBRMetallicRoughnessMaterial.baseColor[i], 3);
+                }
+                if (babylonPBRMetallicRoughnessMaterial.baseTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseTexture=null", 3);
+                }
+
+                // Metallic+roughness
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallic=" + babylonPBRMetallicRoughnessMaterial.metallic, 3);
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.roughness=" + babylonPBRMetallicRoughnessMaterial.roughness, 3);
+                if (babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture=null", 3);
+                }
+
+                // Environment
+                if (babylonPBRMetallicRoughnessMaterial.environmentTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.environmentTexture=null", 3);
+                }
+
+                // Normal / bump
+                if (babylonPBRMetallicRoughnessMaterial.normalTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.normalTexture=null", 3);
+                }
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.invertNormalMapX=" + babylonPBRMetallicRoughnessMaterial.invertNormalMapX, 3);
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.invertNormalMapY=" + babylonPBRMetallicRoughnessMaterial.invertNormalMapY, 3);
+
+                // Emissive
+                for (int i = 0; i < babylonPBRMetallicRoughnessMaterial.emissiveColor.Length; i++)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.emissiveColor[" + i + "]=" + babylonPBRMetallicRoughnessMaterial.emissiveColor[i], 3);
+                }
+                if (babylonPBRMetallicRoughnessMaterial.emissiveTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.emissiveTexture=null", 3);
+                }
+
+                // Ambient occlusion
+                RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.occlusionStrength=" + babylonPBRMetallicRoughnessMaterial.occlusionStrength, 3);
+                if (babylonPBRMetallicRoughnessMaterial.occlusionTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.occlusionTexture=null", 3);
+                }
+
+
+                // --------------------------------
+                // --------- gltfMaterial ---------
+                // --------------------------------
+
+                RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2);
+                var gltfMaterial = new GLTFMaterial
+                {
+                    name = name
+                };
+                gltfMaterial.id = babylonMaterial.id;
+                gltfMaterial.index = gltf.MaterialsList.Count;
+                gltf.MaterialsList.Add(gltfMaterial);
+
+                // Alpha
+                string alphaMode;
+                float? alphaCutoff;
+                getAlphaMode(babylonPBRMetallicRoughnessMaterial, out alphaMode, out alphaCutoff);
+                gltfMaterial.alphaMode = alphaMode;
+                gltfMaterial.alphaCutoff = alphaCutoff;
+
+                // DoubleSided
+                gltfMaterial.doubleSided = babylonPBRMetallicRoughnessMaterial.doubleSided;
+
+                // Normal
+                gltfMaterial.normalTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.normalTexture, gltf);
+
+                // Occulison
+                gltfMaterial.occlusionTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.occlusionTexture, gltf);
+
+                // Emissive
+                gltfMaterial.emissiveFactor = babylonPBRMetallicRoughnessMaterial.emissiveColor;
+                gltfMaterial.emissiveTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.emissiveTexture, gltf);
+
+
+                // --------------------------------
+                // --- gltfPbrMetallicRoughness ---
+                // --------------------------------
+
+                RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2);
+                var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
+                gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;
+
+                // --- Global ---
+
+                // Base color
+                gltfPbrMetallicRoughness.baseColorFactor = new float[4]
+                {
+                    babylonPBRMetallicRoughnessMaterial.baseColor[0],
+                    babylonPBRMetallicRoughnessMaterial.baseColor[1],
+                    babylonPBRMetallicRoughnessMaterial.baseColor[2],
+                    1.0f // TODO - alpha
+                };
+                gltfPbrMetallicRoughness.baseColorTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.baseTexture, gltf);
+
+                // Metallic roughness
+                gltfPbrMetallicRoughness.metallicFactor = babylonPBRMetallicRoughnessMaterial.metallic;
+                gltfPbrMetallicRoughness.roughnessFactor = babylonPBRMetallicRoughnessMaterial.roughness;
+                gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture, gltf);
+            }
+            else
+            {
+                RaiseWarning("GLTFExporter.Material | Unsupported material type: " + babylonMaterial.GetType(), 2);
+            }
         }
 
         private void getAlphaMode(BabylonStandardMaterial babylonMaterial, out string alphaMode, out float? alphaCutoff)
@@ -249,6 +389,21 @@ namespace Max2Babylon
             alphaCutoff = null;
         }
 
+        private void getAlphaMode(BabylonPBRMetallicRoughnessMaterial babylonMaterial, out string alphaMode, out float? alphaCutoff)
+        {
+            if (babylonMaterial.baseTexture != null && babylonMaterial.baseTexture.hasAlpha)
+            {
+                // TODO - Babylon standard material is assumed to useAlphaFromDiffuseTexture. If not, the alpha mode is a mask.
+                alphaMode = GLTFMaterial.AlphaMode.BLEND.ToString();
+            }
+            else
+            {
+                // glTF alpha mode default value is "OPAQUE"
+                alphaMode = null; // GLTFMaterial.AlphaMode.OPAQUE.ToString();
+            }
+            alphaCutoff = null;
+        }
+
         BabylonColor3 dielectricSpecular = new BabylonColor3(0.04f, 0.04f, 0.04f);
         const float epsilon = 1e-6f;
 

+ 103 - 75
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Mesh.cs

@@ -92,64 +92,89 @@ namespace Max2Babylon
             gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
 
             // Buffer
-            var buffer = new GLTFBuffer
+            var buffer = gltf.buffer;
+            if (buffer == null)
             {
-                uri = gltfMesh.name + ".bin"
-            };
-            buffer.index = gltf.BuffersList.Count;
-            gltf.BuffersList.Add(buffer);
+                buffer = new GLTFBuffer
+                {
+                    uri = gltf.OutputFile + ".bin"
+                };
+                buffer.index = gltf.BuffersList.Count;
+                gltf.BuffersList.Add(buffer);
+                gltf.buffer = buffer;
+            }
 
             // BufferView - Scalar
-            var bufferViewScalar = new GLTFBufferView
+            var bufferViewScalar = gltf.bufferViewScalar;
+            if (bufferViewScalar == null)
             {
-                name = "bufferViewScalar",
-                buffer = buffer.index,
-                Buffer = buffer
-            };
-            bufferViewScalar.index = gltf.BufferViewsList.Count;
-            gltf.BufferViewsList.Add(bufferViewScalar);
+                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 = new GLTFBufferView
+            var bufferViewFloatVec3 = gltf.bufferViewFloatVec3;
+            if (bufferViewFloatVec3 == null)
             {
-                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);
+                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 = new GLTFBufferView
+                bufferViewFloatVec4 = gltf.bufferViewFloatVec4;
+                if (bufferViewFloatVec4 == null)
                 {
-                    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);
+                    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 = new GLTFBufferView
+                bufferViewFloatVec2 = gltf.bufferViewFloatVec2;
+                if (bufferViewFloatVec2 == null)
                 {
-                    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);
+                    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;
+                }
             }
 
             // --------------------------
@@ -391,51 +416,54 @@ namespace Max2Babylon
             // --------- Saving ---------
             // --------------------------
 
-            string outputBinaryFile = Path.Combine(gltf.OutputPath, gltfMesh.name + ".bin");
-            RaiseMessage("GLTFExporter.Mesh | Saving " + outputBinaryFile, 2);
+            RaiseMessage("GLTFExporter.Mesh | saving", 2);
 
-            // Write data to binary file
-            using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
+            // BufferView - Scalar
+            gltfIndices.ForEach(n => bufferViewScalar.bytesList.AddRange(BitConverter.GetBytes(n)));
+
+            // BufferView - Vector3
+            globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
             {
-                // BufferView - Scalar
-                gltfIndices.ForEach(n => writer.Write(n));
+                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)));
 
-                // BufferView - Vector3
-                globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
-                {
-                    List<float> vertices = globalVerticesSubMesh.SelectMany(v => new[] { v.Position.X, v.Position.Y, v.Position.Z }).ToList();
-                    vertices.ForEach(n => writer.Write(n));
+                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)));
+            });
 
-                    List<float> normals = globalVerticesSubMesh.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToList();
-                    normals.ForEach(n => writer.Write(n));
-                });
+            // BufferView - Vector4
+            globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
+            {
+                if (hasColor)
+                {
+                    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)));
+                }
+            });
 
-                // BufferView - Vector4
-                globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
+            // BufferView - Vector2
+            globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
+            {
+                if (hasUV)
                 {
-                    if (hasColor)
-                    {
-                        List<float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
-                        colors.ForEach(n => writer.Write(n));
-                    }
-                });
+                    List<float> uvs = globalVerticesSubMesh.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList();
+                    uvs.ForEach(n => bufferViewFloatVec2.bytesList.AddRange(BitConverter.GetBytes(n)));
+                }
 
-                // BufferView - Vector2
-                globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
+                if (hasUV2)
                 {
-                    if (hasUV)
-                    {
-                        List<float> uvs = globalVerticesSubMesh.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList();
-                        uvs.ForEach(n => writer.Write(n));
-                    }
-                    
-                    if (hasUV2)
-                    {
-                        List<float> uvs2 = globalVerticesSubMesh.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList();
-                        uvs2.ForEach(n => writer.Write(n));
-                    }
-                });
-            }
+                    List<float> uvs2 = globalVerticesSubMesh.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList();
+                    uvs2.ForEach(n => bufferViewFloatVec2.bytesList.AddRange(BitConverter.GetBytes(n)));
+                }
+            });
+
+            //// 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));
+            //}
 
             return gltfMesh;
         }

+ 12 - 2
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Texture.cs

@@ -21,9 +21,10 @@ namespace Max2Babylon
             // Copy image to output
             if (CopyTexturesToOutput)
             {
-                var absolutePath = Path.Combine(gltf.OutputPath, name);
+                var absolutePath = Path.Combine(gltf.OutputFolder, name);
+                var imageFormat = Path.GetExtension(name) == ".jpg" ? System.Drawing.Imaging.ImageFormat.Jpeg : System.Drawing.Imaging.ImageFormat.Png;
                 RaiseMessage($"GLTFExporter.Texture | write image '{name}' to '{absolutePath}'", 1);
-                bitmap.Save(absolutePath);
+                bitmap.Save(absolutePath, imageFormat);
             }
 
             return ExportTexture(babylonTexture, gltf, name);
@@ -76,6 +77,15 @@ namespace Max2Babylon
 
             gltfImage.index = gltf.ImagesList.Count;
             gltf.ImagesList.Add(gltfImage);
+            switch (Path.GetExtension(name))
+            {
+                case ".jpg":
+                    gltfImage.FileExtension = "jpeg";
+                    break;
+                case ".png":
+                    gltfImage.FileExtension = "png";
+                    break;
+            }
 
 
             // --------------------------

+ 167 - 16
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.cs

@@ -3,6 +3,7 @@ using GLTFExport.Entities;
 using Newtonsoft.Json;
 using System;
 using System.Collections.Generic;
+using System.Drawing;
 using System.Globalization;
 using System.IO;
 using System.Text;
@@ -16,7 +17,7 @@ namespace Max2Babylon
 
         private List<BabylonNode> babylonNodes;
 
-        public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary)
+        public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary, bool exportGltfImagesAsBinary)
         {
             RaiseMessage("GLTFExporter | Export outputFile=" + outputFile + " generateBinary=" + generateBinary);
             RaiseMessage("GLTFExporter | Exportation started", Color.Blue);
@@ -29,7 +30,7 @@ namespace Max2Babylon
             initBabylonNodes(babylonScene);
             babylonMaterialsToExport = new List<BabylonMaterial>();
 
-            var gltf = new GLTF(Path.GetDirectoryName(outputFile));
+            var gltf = new GLTF(outputFile);
 
             // Asset
             gltf.asset = new GLTFAsset
@@ -96,30 +97,113 @@ namespace Max2Babylon
                 CheckCancelled();
             };
             RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);
+            
+            if (exportGltfImagesAsBinary)
+            {
+                SwitchImagesFromUriToBinary(gltf);
+            }
 
-            // Output
-            RaiseMessage("GLTFExporter | Saving to output file");
             // Cast lists to arrays
             gltf.Prepare();
-            var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings());
-            var sb = new StringBuilder();
-            var sw = new StringWriter(sb, CultureInfo.InvariantCulture);
 
-            // Do not use the optimized writer because it's not necessary to truncate values
-            // Use the bounded writer in case some values are infinity ()
-            using (var jsonWriter = new JsonTextWriterBounded(sw))
+            // Output
+            RaiseMessage("GLTFExporter | Saving to output file");
+            
+            string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
+            File.WriteAllText(outputGltfFile, gltfToJson(gltf));
+
+            // Write data to binary file
+            string outputBinaryFile = Path.ChangeExtension(outputFile, "bin");
+            using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
             {
-                jsonWriter.Formatting = Formatting.None;
-                jsonSerializer.Serialize(jsonWriter, gltf);
+                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);
+                    });
+                });
             }
-            string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
-            File.WriteAllText(outputGltfFile, sb.ToString());
 
             // Binary
             if (generateBinary)
             {
-                // TODO - Export glTF data to binary format .glb
-                RaiseError("GLTFExporter | TODO - Generating binary files");
+                // Export glTF data to binary format .glb
+                RaiseMessage("GLTFExporter | Generating .glb file");
+
+                // Header
+                UInt32 magic = 0x46546C67; // ASCII code for glTF
+                UInt32 version = 2;
+                UInt32 length = 12; // Header length
+
+                // --- JSON chunk ---
+                UInt32 chunkTypeJson = 0x4E4F534A; // ASCII code for JSON
+                // Remove buffers uri
+                foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
+                {
+                    gltfBuffer.uri = null;
+                }
+                // Switch images to binary if not already done
+                // TODO - make it optional
+                if (!exportGltfImagesAsBinary)
+                {
+                    var imageBufferViews = SwitchImagesFromUriToBinary(gltf);
+                    imageBufferViews.ForEach(imageBufferView =>
+                    {
+                        imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList);
+                    });
+                }
+                gltf.Prepare();
+                // Serialize gltf data to JSON string then convert it to bytes
+                byte[] chunkDataJson = Encoding.ASCII.GetBytes(gltfToJson(gltf));
+                // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements 
+                var nbSpaceToAdd = chunkDataJson.Length % 4 == 0 ? 0 : (4 - chunkDataJson.Length % 4);
+                var chunkDataJsonList = new List<byte>(chunkDataJson);
+                for (int i = 0; i < nbSpaceToAdd; i++)
+                {
+                    chunkDataJsonList.Add(0x20);
+                }
+                chunkDataJson = chunkDataJsonList.ToArray();
+                UInt32 chunkLengthJson = (UInt32)chunkDataJson.Length;
+                length += chunkLengthJson + 8; // 8 = JSON chunk header length
+                
+                // bin chunk
+                UInt32 chunkTypeBin = 0x004E4942; // ASCII code for BIN
+                UInt32 chunkLengthBin = 0;
+                if (gltf.BuffersList.Count > 0)
+                {
+                    foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
+                    {
+                        chunkLengthBin += (uint)gltfBuffer.byteLength;
+                    }
+                    length += chunkLengthBin + 8; // 8 = bin chunk header length
+                }
+                
+
+                // Write binary file
+                string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
+                using (BinaryWriter writer = new BinaryWriter(File.Open(outputGlbFile, FileMode.Create)))
+                {
+                    // Header
+                    writer.Write(magic);
+                    writer.Write(version);
+                    writer.Write(length);
+                    
+                    // JSON chunk
+                    writer.Write(chunkLengthJson);
+                    writer.Write(chunkTypeJson);
+                    writer.Write(chunkDataJson);
+
+                    // bin chunk
+                    if (gltf.BuffersList.Count > 0)
+                    {
+                        writer.Write(chunkLengthBin);
+                        writer.Write(chunkTypeBin);
+                        gltf.BuffersList[0].bytesList.ForEach(b => writer.Write(b));
+                    }
+                };
             }
 
             ReportProgressChanged(100);
@@ -236,5 +320,72 @@ namespace Max2Babylon
             // No relevant node found in hierarchy
             return false;
         }
+
+        private string gltfToJson(GLTF gltf)
+        {
+            var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings());
+            var sb = new StringBuilder();
+            var sw = new StringWriter(sb, CultureInfo.InvariantCulture);
+
+            // Do not use the optimized writer because it's not necessary to truncate values
+            // Use the bounded writer in case some values are infinity ()
+            using (var jsonWriter = new JsonTextWriterBounded(sw))
+            {
+                jsonWriter.Formatting = Formatting.None;
+                jsonSerializer.Serialize(jsonWriter, gltf);
+            }
+            return sb.ToString();
+        }
+
+        private List<GLTFBufferView> SwitchImagesFromUriToBinary(GLTF gltf)
+        {
+            var imageBufferViews = new List<GLTFBufferView>();
+
+            foreach (GLTFImage gltfImage in gltf.ImagesList)
+            {
+                var path = Path.Combine(gltf.OutputFolder, gltfImage.uri);
+                using (Image image = Image.FromFile(path))
+                {
+                    using (MemoryStream m = new MemoryStream())
+                    {
+                        var imageFormat = gltfImage.FileExtension == "jpeg" ? System.Drawing.Imaging.ImageFormat.Jpeg : System.Drawing.Imaging.ImageFormat.Png;
+                        image.Save(m, imageFormat);
+                        byte[] imageBytes = m.ToArray();
+
+                        // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements 
+                        var nbSpaceToAdd = imageBytes.Length % 4 == 0 ? 0 : (4 - imageBytes.Length % 4);
+                        var imageBytesList = new List<byte>(imageBytes);
+                        for (int i = 0; i < nbSpaceToAdd; i++)
+                        {
+                            imageBytesList.Add(0x00);
+                        }
+                        imageBytes = imageBytesList.ToArray();
+
+                        // BufferView - Image
+                        var buffer = gltf.buffer;
+                        var bufferViewImage = new GLTFBufferView
+                        {
+                            name = "bufferViewImage",
+                            buffer = buffer.index,
+                            Buffer = buffer,
+                            byteOffset = buffer.byteLength
+                        };
+                        bufferViewImage.index = gltf.BufferViewsList.Count;
+                        gltf.BufferViewsList.Add(bufferViewImage);
+                        imageBufferViews.Add(bufferViewImage);
+
+
+                        gltfImage.uri = null;
+                        gltfImage.bufferView = bufferViewImage.index;
+                        gltfImage.mimeType = "image/" + gltfImage.FileExtension;
+
+                        bufferViewImage.bytesList.AddRange(imageBytes);
+                        bufferViewImage.byteLength += imageBytes.Length;
+                        bufferViewImage.Buffer.byteLength += imageBytes.Length;
+                    }
+                }
+            }
+            return imageBufferViews;
+        }
     }
 }

+ 173 - 18
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Material.cs

@@ -16,6 +16,65 @@ namespace Max2Babylon
 
             RaiseMessage(name, 1);
 
+            // --- prints ---
+            {
+                RaiseMessage("materialNode.MaterialClass=" + materialNode.MaterialClass, 2);
+                RaiseMessage("materialNode.NumberOfTextureMaps=" + materialNode.NumberOfTextureMaps, 2);
+
+                var propertyContainer = materialNode.IPropertyContainer;
+                RaiseMessage("propertyContainer=" + propertyContainer, 2);
+                if (propertyContainer != null)
+                {
+                    RaiseMessage("propertyContainer.NumberOfProperties=" + propertyContainer.NumberOfProperties, 3);
+                    for (int i = 0; i < propertyContainer.NumberOfProperties; i++)
+                    {
+                        var prop = propertyContainer.GetProperty(i);
+                        if (prop != null)
+                        {
+                            RaiseMessage("propertyContainer.GetProperty(" + i + ")=" + prop.Name, 3);
+                            switch (prop.GetType_)
+                            {
+                                case PropType.StringProp:
+                                    string propertyString = "";
+                                    RaiseMessage("prop.GetPropertyValue(ref propertyString, 0)=" + prop.GetPropertyValue(ref propertyString, 0), 4);
+                                    RaiseMessage("propertyString=" + propertyString, 4);
+                                    break;
+                                case PropType.IntProp:
+                                    int propertyInt = 0;
+                                    RaiseMessage("prop.GetPropertyValue(ref propertyInt, 0)=" + prop.GetPropertyValue(ref propertyInt, 0), 4);
+                                    RaiseMessage("propertyInt=" + propertyInt, 4);
+                                    break;
+                                case PropType.FloatProp:
+                                    float propertyFloat = 0;
+                                    RaiseMessage("prop.GetPropertyValue(ref propertyFloat, 0)=" + prop.GetPropertyValue(ref propertyFloat, 0, true), 4);
+                                    RaiseMessage("propertyFloat=" + propertyFloat, 4);
+                                    RaiseMessage("prop.GetPropertyValue(ref propertyFloat, 0)=" + prop.GetPropertyValue(ref propertyFloat, 0, false), 4);
+                                    RaiseMessage("propertyFloat=" + propertyFloat, 4);
+                                    break;
+                                case PropType.Point3Prop:
+                                    IPoint3 propertyPoint3 = Loader.Global.Point3.Create(0, 0, 0); 
+                                    RaiseMessage("prop.GetPropertyValue(ref propertyPoint3, 0)=" + prop.GetPropertyValue(propertyPoint3, 0), 4);
+                                    RaiseMessage("propertyPoint3=" + Point3ToString(propertyPoint3), 4);
+                                    break;
+                                case PropType.Point4Prop:
+                                    IPoint4 propertyPoint4 = Loader.Global.Point4.Create(0,0,0,0);
+                                    RaiseMessage("prop.GetPropertyValue(ref propertyPoint4, 0)=" + prop.GetPropertyValue(propertyPoint4, 0), 4);
+                                    RaiseMessage("propertyPoint4=" + Point4ToString(propertyPoint4), 4);
+                                    break;
+                                case PropType.UnknownProp:
+                                default:
+                                    RaiseMessage("Unknown property type", 4);
+                                    break;
+                            }
+                        }
+                        else
+                        {
+                            RaiseMessage("propertyContainer.GetProperty(" + i + ") IS NULL", 3);
+                        }
+                    }
+                }
+            }
+
             if (materialNode.SubMaterialCount > 0)
             {
                 var babylonMultimaterial = new BabylonMultiMaterial { name = name, id = id };
@@ -47,27 +106,26 @@ namespace Max2Babylon
                 babylonScene.MultiMaterialsList.Add(babylonMultimaterial);
                 return;
             }
-
-            var babylonMaterial = new BabylonStandardMaterial
-            {
-                name = name,
-                id = id,
-                ambient = materialNode.MaxMaterial.GetAmbient(0, false).ToArray(),
-                diffuse = materialNode.MaxMaterial.GetDiffuse(0, false).ToArray(),
-                specular = materialNode.MaxMaterial.GetSpecular(0, false).Scale(materialNode.MaxMaterial.GetShinStr(0, false)),
-                specularPower = materialNode.MaxMaterial.GetShininess(0, false) * 256,
-                emissive =
-                    materialNode.MaxMaterial.GetSelfIllumColorOn(0, false)
-                        ? materialNode.MaxMaterial.GetSelfIllumColor(0, false).ToArray()
-                        : materialNode.MaxMaterial.GetDiffuse(0, false).Scale(materialNode.MaxMaterial.GetSelfIllum(0, false)),
-                alpha = 1.0f - materialNode.MaxMaterial.GetXParency(0, false)
-            };
-
-
+            
             var stdMat = materialNode.MaxMaterial.GetParamBlock(0).Owner as IStdMat2;
 
             if (stdMat != null)
             {
+                var babylonMaterial = new BabylonStandardMaterial
+                {
+                    name = name,
+                    id = id,
+                    ambient = materialNode.MaxMaterial.GetAmbient(0, false).ToArray(),
+                    diffuse = materialNode.MaxMaterial.GetDiffuse(0, false).ToArray(),
+                    specular = materialNode.MaxMaterial.GetSpecular(0, false).Scale(materialNode.MaxMaterial.GetShinStr(0, false)),
+                    specularPower = materialNode.MaxMaterial.GetShininess(0, false) * 256,
+                    emissive =
+                        materialNode.MaxMaterial.GetSelfIllumColorOn(0, false)
+                            ? materialNode.MaxMaterial.GetSelfIllumColor(0, false).ToArray()
+                            : materialNode.MaxMaterial.GetDiffuse(0, false).Scale(materialNode.MaxMaterial.GetSelfIllum(0, false)),
+                    alpha = 1.0f - materialNode.MaxMaterial.GetXParency(0, false)
+                };
+
                 babylonMaterial.backFaceCulling = !stdMat.TwoSided;
                 babylonMaterial.wireframe = stdMat.Wire;
 
@@ -141,9 +199,106 @@ namespace Max2Babylon
                     RaiseWarning("Opacity texture was removed because alpha from diffuse texture can be use instead", 2);
                     RaiseWarning("If you do not want this behavior, just set Alpha Source = None on your diffuse texture", 2);
                 }
+
+                babylonScene.MaterialsList.Add(babylonMaterial);
+            }
+            else if (materialNode.MaterialClass == "Physical Material")
+            {
+                var propertyContainer = materialNode.IPropertyContainer;
+
+                var babylonMaterial = new BabylonPBRMetallicRoughnessMaterial
+                {
+                    name = name,
+                    id = id
+                };
+
+                // --- Global ---
+
+                babylonMaterial.alpha = 1.0f - materialNode.MaxMaterial.GetXParency(0, false);
+
+                // TODO - Add alpha
+                babylonMaterial.baseColor = materialNode.MaxMaterial.GetDiffuse(0, false).ToArray();
+
+                babylonMaterial.metallic = propertyContainer.GetFloatProperty(6);
+
+                babylonMaterial.roughness = propertyContainer.GetFloatProperty(4);
+                if (propertyContainer.GetIntProperty(5) == 1)
+                {
+                    // Inverse roughness
+                    babylonMaterial.roughness = 1 - babylonMaterial.roughness;
+                }
+
+                // Self illumination is computed from emission color, luminance, temperature and weight
+                babylonMaterial.emissiveColor = materialNode.MaxMaterial.GetSelfIllumColorOn(0, false)
+                                                ? materialNode.MaxMaterial.GetSelfIllumColor(0, false).ToArray()
+                                                : materialNode.MaxMaterial.GetDiffuse(0, false).Scale(materialNode.MaxMaterial.GetSelfIllum(0, false));
+
+                // TODO - occlusionStrength - use default? ignored?
+
+                // TODO - alphaCutOff - use default?
+
+                // TODO - transparencyMode - private or public ?
+
+                // TODO - doubleSided - use default?
+
+                // --- Textures ---
+
+                // TODO - Add alpha
+                babylonMaterial.baseTexture = ExportPBRTexture(materialNode, 1, babylonScene);
+
+                babylonMaterial.metallicRoughnessTexture = ExportMetallicRoughnessTexture(materialNode, babylonMaterial.metallic, babylonMaterial.roughness, babylonScene, name);
+
+                // TODO - environmentTexture - as simple as that?
+                babylonMaterial.environmentTexture = ExportPBRTexture(materialNode, 3, babylonScene);
+
+                var normalMapAmount = propertyContainer.GetFloatProperty(91);
+                babylonMaterial.normalTexture = ExportPBRTexture(materialNode, 30, babylonScene, normalMapAmount);
+                
+                babylonMaterial.emissiveTexture = ExportPBRTexture(materialNode, 17, babylonScene);
+                
+                // TODO - occlusionTexture - ignored?
+
+
+                babylonScene.MaterialsList.Add(babylonMaterial);
+            }
+            else
+            {
+                RaiseWarning("Unsupported material type: " + materialNode.MaterialClass, 2);
+            }
+        }
+
+        // -------------------------
+        // --------- Utils ---------
+        // -------------------------
+
+        private string ColorToString(IColor color)
+        {
+            if (color == null)
+            {
+                return "";
+            }
+
+            return "{ r=" + color.R + ", g=" + color.G + ", b=" + color.B + " }";
+        }
+
+        private string Point3ToString(IPoint3 point)
+        {
+            if (point == null)
+            {
+                return "";
+            }
+
+            return "{ x=" + point.X + ", y=" + point.Y + ", z=" + point.Z + " }";
+        }
+
+        private string Point4ToString(IPoint4 point)
+        {
+            if (point == null)
+            {
+                return "";
             }
 
-            babylonScene.MaterialsList.Add(babylonMaterial);
+            return "{ x=" + point.X + ", y=" + point.Y + ", z=" + point.Z + ", w=" + point.W + " }";
         }
     }
 }

+ 324 - 82
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Texture.cs

@@ -3,77 +3,226 @@ using System.Collections.Generic;
 using System.IO;
 using Autodesk.Max;
 using BabylonExport.Entities;
+using System.Drawing;
 
 namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        bool IsTextureCube(string filepath)
+        // -------------------------------
+        // --- "public" export methods ---
+        // -------------------------------
+
+        private BabylonTexture ExportTexture(IStdMat2 stdMat, int index, out BabylonFresnelParameters fresnelParameters, BabylonScene babylonScene, bool allowCube = false, bool forceAlpha = false)
         {
-            try
+            fresnelParameters = null;
+
+            if (!stdMat.MapEnabled(index))
             {
-                if (Path.GetExtension(filepath).ToLower() != ".dds")
-                {
-                    return false;
-                }
+                return null;
+            }
 
-                var data = File.ReadAllBytes(filepath);
-                var intArray = new int[data.Length / 4];
+            var texMap = stdMat.GetSubTexmap(index);
 
-                Buffer.BlockCopy(data, 0, intArray, 0, intArray.Length * 4);
+            if (texMap == null)
+            {
+                RaiseWarning("Texture channel " + index + " activated but no texture found.", 2);
+                return null;
+            }
 
+            texMap = _exportFresnelParameters(texMap, out fresnelParameters);
 
-                int width = intArray[4];
-                int height = intArray[3];
-                int mipmapsCount = intArray[7];
+            var amount = stdMat.GetTexmapAmt(index, 0);
 
-                if ((width >> (mipmapsCount - 1)) > 1)
-                {
-                    var expected = 1;
-                    var currentSize = Math.Max(width, height);
+            return _exportTexture(texMap, amount, babylonScene, allowCube, forceAlpha);
+        }
 
-                    while (currentSize > 1)
-                    {
-                        currentSize = currentSize >> 1;
-                        expected++;
-                    }
+        private BabylonTexture ExportPBRTexture(IIGameMaterial materialNode, int index, BabylonScene babylonScene, float amount = 1.0f)
+        {
+            var texMap = _getTexMap(materialNode, index);
+            if (texMap != null)
+            {
+                return _exportTexture(texMap, amount, babylonScene);
+            }
+            return null;
+        }
 
-                    RaiseWarning(string.Format("Mipmaps chain is not complete: {0} maps instead of {1} (based on texture max size: {2})", mipmapsCount, expected, width), 2);
-                    RaiseWarning(string.Format("You must generate a complete mipmaps chain for .dds)"), 2);
-                    RaiseWarning(string.Format("Mipmaps will be disabled for this texture. If you want automatic texture generation you cannot use a .dds)"), 2);
-                }
+        private BabylonTexture ExportMetallicRoughnessTexture(IIGameMaterial materialNode, float metallic, float roughness, BabylonScene babylonScene, string materialName)
+        {
+            ITexmap metallicTexMap = _getTexMap(materialNode, 5);
+            ITexmap roughnessTexMap = _getTexMap(materialNode, 4);
 
-                bool isCube = (intArray[28] & 0x200) == 0x200;
+            if (metallicTexMap == null && roughnessTexMap == null)
+            {
+                return null;
+            }
 
-                return isCube;
+            // Use one as a reference for UVs parameters
+            var referenceTexMap = metallicTexMap != null ? metallicTexMap : roughnessTexMap;
+
+
+            // --- Babylon texture ---
+
+            if (referenceTexMap.GetParamBlock(0) == null || referenceTexMap.GetParamBlock(0).Owner == null)
+            {
+                return null;
             }
-            catch
+
+            var texture = referenceTexMap.GetParamBlock(0).Owner as IBitmapTex;
+
+            if (texture == null)
             {
-                return false;
+                return null;
             }
+
+            var babylonTexture = new BabylonTexture
+            {
+                name = materialName + "_metallicRoughness" + ".jpg" // TODO - unsafe name, may conflict with another texture name
+            };
+
+            // Level
+            babylonTexture.level = 1.0f;
+
+            // No alpha
+            babylonTexture.hasAlpha = false;
+            babylonTexture.getAlphaFromRGB = false;
+
+            // UVs
+            var uvGen = _exportUV(texture, babylonTexture);
+
+            // Is cube
+            _exportIsCube(texture, babylonTexture, false);
+
+
+            // --- Merge metallic and roughness maps ---
+
+            // Load bitmaps
+            var metallicBitmap = _loadTexture(metallicTexMap);
+            var roughnessBitmap = _loadTexture(roughnessTexMap);
+
+            // Retreive dimensions
+            int width = 0;
+            int height = 0;
+            var haveSameDimensions = _getMinimalBitmapDimensions(out width, out height, metallicBitmap, roughnessBitmap);
+            if (!haveSameDimensions)
+            {
+                RaiseWarning("Metallic and roughness maps should have same dimensions", 2);
+            }
+
+            // Create metallic+roughness map
+            Bitmap metallicRoughnessBitmap = new Bitmap(width, height);
+            for (int x = 0; x < width; x++)
+            {
+                for (int y = 0; y < height; y++)
+                {
+                    var _metallic = metallicBitmap != null ? metallicBitmap.GetPixel(x, y).R :
+                                    metallic * 255.0f;
+                    var _roughness = roughnessBitmap != null ? roughnessBitmap.GetPixel(x, y).R :
+                                    roughness * 255.0f;
+
+                    // 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)_roughness,
+                        (int)_metallic
+                    );
+                    metallicRoughnessBitmap.SetPixel(x, y, colorMetallicRoughness);
+                }
+            }
+
+            // Write bitmap
+            var absolutePath = Path.Combine(babylonScene.OutputPath, babylonTexture.name);
+            RaiseMessage($"Texture | write image '{babylonTexture.name}'", 2);
+            metallicRoughnessBitmap.Save(absolutePath);
+
+            return babylonTexture;
         }
 
-        private BabylonTexture ExportTexture(IStdMat2 stdMat, int index, out BabylonFresnelParameters fresnelParameters, BabylonScene babylonScene, bool allowCube = false, bool forceAlpha = false)
-        {
-            fresnelParameters = null;
+        // -------------------------
+        // -- Export sub methods ---
+        // -------------------------
 
-            if (!stdMat.MapEnabled(index))
+        private BabylonTexture _exportTexture(ITexmap texMap, float amount, BabylonScene babylonScene, bool allowCube = false, bool forceAlpha = false)
+        {
+            if (texMap.GetParamBlock(0) == null || texMap.GetParamBlock(0).Owner == null)
             {
                 return null;
             }
-            var babylonTexture = new BabylonTexture();
 
-            var texMap = stdMat.GetSubTexmap(index);
+            var texture = texMap.GetParamBlock(0).Owner as IBitmapTex;
 
-            if (texMap == null)
+            if (texture == null)
             {
-                RaiseWarning("Texture channel " + index + " activated but no texture found.");
                 return null;
             }
 
+            var babylonTexture = new BabylonTexture
+            {
+                name = Path.GetFileName(texture.MapName)
+            };
+
+            // Level
+            babylonTexture.level = amount;
+
+            // Alpha
+            if (forceAlpha)
+            {
+                babylonTexture.hasAlpha = true;
+                babylonTexture.getAlphaFromRGB = (texture.AlphaSource == 2) || (texture.AlphaSource == 3);
+            }
+            else
+            {
+                babylonTexture.hasAlpha = (texture.AlphaSource != 3);
+                babylonTexture.getAlphaFromRGB = (texture.AlphaSource == 2);
+            }
+
+            // UVs
+            var uvGen = _exportUV(texture, babylonTexture);
+
+            // Animations
+            var animations = new List<BabylonAnimation>();
+            ExportFloatAnimation("uOffset", animations, key => new[] { uvGen.GetUOffs(key) });
+            ExportFloatAnimation("vOffset", animations, key => new[] { -uvGen.GetVOffs(key) });
+            ExportFloatAnimation("uScale", animations, key => new[] { uvGen.GetUScl(key) });
+            ExportFloatAnimation("vScale", animations, key => new[] { uvGen.GetVScl(key) });
+            ExportFloatAnimation("uAng", animations, key => new[] { uvGen.GetUAng(key) });
+            ExportFloatAnimation("vAng", animations, key => new[] { uvGen.GetVAng(key) });
+            ExportFloatAnimation("wAng", animations, key => new[] { uvGen.GetWAng(key) });
+            babylonTexture.animations = animations.ToArray();
+
+            // Is cube
+            _exportIsCube(texture, babylonTexture, allowCube);
+
+            // Copy texture to output
+            var absolutePath = texture.Map.FullFilePath;
+            try
+            {
+                if (File.Exists(absolutePath))
+                {
+                    if (CopyTexturesToOutput)
+                    {
+                        File.Copy(absolutePath, Path.Combine(babylonScene.OutputPath, babylonTexture.name), true);
+                    }
+                }
+            }
+            catch
+            {
+                // silently fails
+            }
+
+            return babylonTexture;
+        }
+
+        private ITexmap _exportFresnelParameters(ITexmap texMap, out BabylonFresnelParameters fresnelParameters)
+        {
+            fresnelParameters = null;
+
             // Fallout
             if (texMap.ClassName == "Falloff") // This is the only way I found to detect it. This is crappy but it works
             {
+                RaiseMessage("fresnelParameters", 2);
                 fresnelParameters = new BabylonFresnelParameters();
 
                 var paramBlock = texMap.GetParamBlock(0);
@@ -119,33 +268,11 @@ namespace Max2Babylon
                 }
             }
 
-            // Bitmap
-            if (texMap.GetParamBlock(0) == null || texMap.GetParamBlock(0).Owner == null)
-            {
-                return null;
-            }
-
-            var texture = texMap.GetParamBlock(0).Owner as IBitmapTex;
-
-            if (texture == null)
-            {
-                return null;
-            }
-
-            if (forceAlpha)
-            {
-                babylonTexture.hasAlpha = true;
-                babylonTexture.getAlphaFromRGB = (texture.AlphaSource == 2) || (texture.AlphaSource == 3);
-            }
-            else
-            {
-                babylonTexture.hasAlpha = (texture.AlphaSource != 3);
-                babylonTexture.getAlphaFromRGB = (texture.AlphaSource == 2);
-            }
-
-
-            babylonTexture.level = stdMat.GetTexmapAmt(index, 0);
+            return texMap;
+        }
 
+        private IStdUVGen _exportUV(IBitmapTex texture, BabylonTexture babylonTexture)
+        {
             var uvGen = texture.UVGen;
 
             switch (uvGen.GetCoordMapping(0))
@@ -202,30 +329,18 @@ namespace Max2Babylon
                 babylonTexture.wrapV = BabylonTexture.AddressMode.MIRROR_ADDRESSMODE;
             }
 
-            babylonTexture.name = Path.GetFileName(texture.MapName);
-
-            // Animations
-            var animations = new List<BabylonAnimation>();
-            ExportFloatAnimation("uOffset", animations, key => new[] { uvGen.GetUOffs(key) });
-            ExportFloatAnimation("vOffset", animations, key => new[] { -uvGen.GetVOffs(key) });
-            ExportFloatAnimation("uScale", animations, key => new[] { uvGen.GetUScl(key) });
-            ExportFloatAnimation("vScale", animations, key => new[] { uvGen.GetVScl(key) });
-            ExportFloatAnimation("uAng", animations, key => new[] { uvGen.GetUAng(key) });
-            ExportFloatAnimation("vAng", animations, key => new[] { uvGen.GetVAng(key) });
-            ExportFloatAnimation("wAng", animations, key => new[] { uvGen.GetWAng(key) });
+            return uvGen;
+        }
 
-            babylonTexture.animations = animations.ToArray();
+        private void _exportIsCube(IBitmapTex texture, BabylonTexture babylonTexture, bool allowCube)
+        {
             var absolutePath = texture.Map.FullFilePath;
-            // Copy texture to output
+
             try
             {
                 if (File.Exists(absolutePath))
                 {
-                    babylonTexture.isCube = IsTextureCube(absolutePath);
-                    if (CopyTexturesToOutput)
-                    {
-                        File.Copy(absolutePath, Path.Combine(babylonScene.OutputPath, babylonTexture.name), true);
-                    }
+                    babylonTexture.isCube = _isTextureCube(absolutePath);
                 }
                 else
                 {
@@ -242,8 +357,135 @@ namespace Max2Babylon
             {
                 RaiseWarning(string.Format("Cube texture are only supported for reflection channel"), 2);
             }
+        }
 
-            return babylonTexture;
+        private bool _isTextureCube(string filepath)
+        {
+            try
+            {
+                if (Path.GetExtension(filepath).ToLower() != ".dds")
+                {
+                    return false;
+                }
+
+                var data = File.ReadAllBytes(filepath);
+                var intArray = new int[data.Length / 4];
+
+                Buffer.BlockCopy(data, 0, intArray, 0, intArray.Length * 4);
+
+
+                int width = intArray[4];
+                int height = intArray[3];
+                int mipmapsCount = intArray[7];
+
+                if ((width >> (mipmapsCount - 1)) > 1)
+                {
+                    var expected = 1;
+                    var currentSize = Math.Max(width, height);
+
+                    while (currentSize > 1)
+                    {
+                        currentSize = currentSize >> 1;
+                        expected++;
+                    }
+
+                    RaiseWarning(string.Format("Mipmaps chain is not complete: {0} maps instead of {1} (based on texture max size: {2})", mipmapsCount, expected, width), 2);
+                    RaiseWarning(string.Format("You must generate a complete mipmaps chain for .dds)"), 2);
+                    RaiseWarning(string.Format("Mipmaps will be disabled for this texture. If you want automatic texture generation you cannot use a .dds)"), 2);
+                }
+
+                bool isCube = (intArray[28] & 0x200) == 0x200;
+
+                return isCube;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        // -------------------------
+        // --------- Utils ---------
+        // -------------------------
+
+        private ITexmap _getTexMap(IIGameMaterial materialNode, int index)
+        {
+            ITexmap texMap = null;
+            if (materialNode.MaxMaterial.SubTexmapOn(index) == 1)
+            {
+                texMap = materialNode.MaxMaterial.GetSubTexmap(index);
+
+                // No warning displayed because by default, physical material in 3ds Max have all maps on
+                // Would be tedious for the user to uncheck all unused maps
+
+                //if (texMap == null)
+                //{
+                //    RaiseWarning("Texture channel " + index + " activated but no texture found.", 2);
+                //}
+            }
+            return texMap;
+        }
+
+        private bool _getMinimalBitmapDimensions(out int width, out int height, params Bitmap[] bitmaps)
+        {
+            var haveSameDimensions = true;
+
+            var bitmapsNoNull = ((new List<Bitmap>(bitmaps)).FindAll(bitmap => bitmap != null)).ToArray();
+            if (bitmapsNoNull.Length > 0)
+            {
+                // Init with first element
+                width = bitmapsNoNull[0].Width;
+                height = bitmapsNoNull[0].Height;
+
+                // Update with others
+                for (int i = 1; i < bitmapsNoNull.Length; i++)
+                {
+                    var bitmap = bitmapsNoNull[i];
+                    if (width != bitmap.Width || height != bitmap.Height)
+                    {
+                        haveSameDimensions = false;
+                    }
+                    width = Math.Min(width, bitmap.Width);
+                    height = Math.Min(height, bitmap.Height);
+                }
+            }
+            else
+            {
+                width = 0;
+                height = 0;
+            }
+
+            return haveSameDimensions;
+        }
+
+        private Bitmap LoadTexture(string absolutePath)
+        {
+            if (File.Exists(absolutePath))
+            {
+                return new Bitmap(absolutePath);
+            }
+            else
+            {
+                RaiseWarning(string.Format("Texture {0} not found.", Path.GetFileName(absolutePath), 2));
+                return null;
+            }
+        }
+
+        private Bitmap _loadTexture(ITexmap texMap)
+        {
+            if (texMap == null || texMap.GetParamBlock(0) == null || texMap.GetParamBlock(0).Owner == null)
+            {
+                return null;
+            }
+
+            var texture = texMap.GetParamBlock(0).Owner as IBitmapTex;
+
+            if (texture == null)
+            {
+                return null;
+            }
+
+            return LoadTexture(texture.Map.FullFilePath);
         }
     }
 }

+ 2 - 11
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.cs

@@ -77,7 +77,7 @@ namespace Max2Babylon
             }
         }
 
-        public async Task ExportAsync(string outputFile, bool generateManifest, bool onlySelected, bool generateBinary, bool exportGltf, Form callerForm)
+        public async Task ExportAsync(string outputFile, bool generateManifest, bool onlySelected, bool generateBinary, bool exportGltf, bool exportGltfImagesAsBinary, Form callerForm)
         {
             var gameConversionManger = Loader.Global.ConversionManager;
             gameConversionManger.CoordSystem = Autodesk.Max.IGameConversionManager.CoordSystem.D3d;
@@ -240,15 +240,6 @@ namespace Max2Babylon
                         babylonScene.fogColor = fog.GetColor(0).ToArray();
                         babylonScene.fogMode = 3;
                     }
-#if !MAX2015 && !MAX2016 && !MAX2017
-                    else
-                    {
-                        var paramBlock = atmospheric.GetReference(0) as IIParamBlock;
-
-                        babylonScene.fogColor = Tools.GetParamBlockValueColor(paramBlock, "Fog Color");
-                        babylonScene.fogMode = 3;
-                    }
-#endif
                     if (babylonMainCamera != null)
                     {
                         babylonScene.fogStart = maxMainCameraObject.GetEnvRange(0, 0, Tools.Forever);
@@ -307,7 +298,7 @@ namespace Max2Babylon
             // Export glTF
             if (exportGltf)
             {
-                ExportGltf(babylonScene, outputFile, generateBinary);
+                ExportGltf(babylonScene, outputFile, generateBinary, exportGltfImagesAsBinary);
             }
 
             watch.Stop();

+ 15 - 0
Exporters/3ds Max/Max2Babylon/Forms/ExporterForm.Designer.cs

@@ -48,6 +48,7 @@
             this.butExportAndRun = new System.Windows.Forms.Button();
             this.butClose = new System.Windows.Forms.Button();
             this.chkGltf = new System.Windows.Forms.CheckBox();
+            this.chkGltfImagesAsBinary = new System.Windows.Forms.CheckBox();
             ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit();
             this.groupBox1.SuspendLayout();
             this.SuspendLayout();
@@ -180,6 +181,7 @@
             // 
             this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
             | System.Windows.Forms.AnchorStyles.Right)));
+            this.groupBox1.Controls.Add(this.chkGltfImagesAsBinary);
             this.groupBox1.Controls.Add(this.chkBinary);
             this.groupBox1.Controls.Add(this.chkOnlySelected);
             this.groupBox1.Controls.Add(this.chkAutoSave);
@@ -277,6 +279,18 @@
             this.chkGltf.UseVisualStyleBackColor = true;
             this.chkGltf.CheckedChanged += new System.EventHandler(this.chkGltf_CheckedChanged);
             // 
+            // chkGltfImageAsBinary
+            // 
+            this.chkGltfImagesAsBinary.AutoSize = true;
+            this.chkGltfImagesAsBinary.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+            this.chkGltfImagesAsBinary.Location = new System.Drawing.Point(166, 127);
+            this.chkGltfImagesAsBinary.Name = "chkGltfImagesAsBinary";
+            this.chkGltfImagesAsBinary.Size = new System.Drawing.Size(158, 17);
+            this.chkGltfImagesAsBinary.TabIndex = 18;
+            this.chkGltfImagesAsBinary.Text = "Export glTF images as binary";
+            this.chkGltfImagesAsBinary.UseVisualStyleBackColor = true;
+            this.chkGltfImagesAsBinary.CheckedChanged += new System.EventHandler(this.checkGltfImagesAsBinary_CheckedChanged);
+            // 
             // ExporterForm
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -330,5 +344,6 @@
         private System.Windows.Forms.Button butClose;
         private System.Windows.Forms.CheckBox chkBinary;
         private System.Windows.Forms.CheckBox chkGltf;
+        private System.Windows.Forms.CheckBox chkGltfImagesAsBinary;
     }
 }

+ 8 - 1
Exporters/3ds Max/Max2Babylon/Forms/ExporterForm.cs

@@ -33,6 +33,7 @@ namespace Max2Babylon
             Tools.PrepareCheckBox(chkOnlySelected, Loader.Core.RootNode, "babylonjs_onlySelected");
             Tools.PrepareCheckBox(chkBinary, Loader.Core.RootNode, "babylonjs_binary");
             Tools.PrepareCheckBox(chkGltf, Loader.Core.RootNode, "babylonjs_exportGltf");
+            Tools.PrepareCheckBox(chkGltfImagesAsBinary, Loader.Core.RootNode, "babylonjs_exportGltfImagesAsBinary");
         }
 
         private void butBrowse_Click(object sender, EventArgs e)
@@ -57,6 +58,7 @@ namespace Max2Babylon
             Tools.UpdateCheckBox(chkOnlySelected, Loader.Core.RootNode, "babylonjs_onlySelected");
             Tools.UpdateCheckBox(chkBinary, Loader.Core.RootNode, "babylonjs_binary");
             Tools.UpdateCheckBox(chkGltf, Loader.Core.RootNode, "babylonjs_exportGltf");
+            Tools.UpdateCheckBox(chkGltfImagesAsBinary, Loader.Core.RootNode, "babylonjs_exportGltfImagesAsBinary");
 
             Loader.Core.RootNode.SetLocalData(txtFilename.Text);
 
@@ -123,7 +125,7 @@ namespace Max2Babylon
                 exporter.AutoSave3dsMaxFile = chkAutoSave.Checked;
                 exporter.ExportHiddenObjects = chkHidden.Checked;
                 exporter.CopyTexturesToOutput = chkCopyTextures.Checked;
-                await exporter.ExportAsync(txtFilename.Text, chkManifest.Checked, chkOnlySelected.Checked, chkBinary.Checked, chkGltf.Checked, this);
+                await exporter.ExportAsync(txtFilename.Text, chkManifest.Checked, chkOnlySelected.Checked, chkBinary.Checked, chkGltf.Checked, chkGltfImagesAsBinary.Checked, this);
             }
             catch (OperationCanceledException)
             {
@@ -232,5 +234,10 @@ namespace Max2Babylon
         {
 
         }
+
+        private void checkGltfImagesAsBinary_CheckedChanged(object sender, EventArgs e)
+        {
+
+        }
     }
 }

+ 47 - 116
Exporters/3ds Max/Max2Babylon/Tools/Tools.cs

@@ -13,13 +13,55 @@ namespace Max2Babylon
 {
     public static class Tools
     {
+        // -------------------------
+        // -- IIPropertyContainer --
+        // -------------------------
+
+        public static string GetStringProperty(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            string value = "";
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(ref value, 0);
+            return value;
+        }
+
+        public static int GetIntProperty(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            int value = 0;
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(ref value, 0);
+            return value;
+        }
+
+        public static bool GetBoolProperty(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            return propertyContainer.GetIntProperty(indexProperty) == 1;
+        }
+
+        public static float GetFloatProperty(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            float value = 0.0f;
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(ref value, 0, true);
+            return value;
+        }
+
+        public static IPoint3 GetPoint3Property(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            IPoint3 value = Loader.Global.Point3.Create(0, 0, 0);
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(value, 0);
+            return value;
+        }
+
+        public static IPoint4 GetPoint4Property(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            IPoint4 value = Loader.Global.Point4.Create(0, 0, 0, 0);
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(value, 0);
+            return value;
+        }
+
+        // -------------------------
+
         public static IntPtr GetNativeHandle(this INativeObject obj)
         {
-#if MAX2015 || MAX2016 || MAX2017
             return obj.NativePointer;
-#else
-            return obj.Handle;
-#endif
 
         }
         static Assembly GetWrappersAssembly()
@@ -76,59 +118,7 @@ namespace Max2Babylon
         }
 
         public static IMatrix3 Identity { get { return Loader.Global.Matrix3.Create(XAxis, YAxis, ZAxis, Origin); } }
-
-#if !MAX2015 && !MAX2016 && !MAX2017
-        unsafe public static int GetParamBlockIndex(IIParamBlock paramBlock, string name)
-        {
-            for (short index = 0; index < paramBlock.NumParams; index++)
-            {
-                IGetParamName gpn = Loader.Global.GetParamName.Create("", index);
-
-                paramBlock.NotifyDependents(Tools.Forever, (UIntPtr)gpn.Handle.ToPointer(), RefMessage.GetParamName, (SClass_ID)0xfffffff0, false, null);
-
-                if (gpn.Name == name)
-                {
-                    return index;
-                }
-            }
-
-            return -1;
-        }
-
-
-        public static int GetParamBlockValueInt(IIParamBlock paramBlock, string name)
-        {
-            var index = Tools.GetParamBlockIndex(paramBlock, name);
-
-            if (index == -1)
-            {
-                return 0;
-            }
-            return paramBlock.GetInt(index, 0);
-        }
-
-        public static float GetParamBlockValueFloat(IIParamBlock paramBlock, string name)
-        {
-            var index = Tools.GetParamBlockIndex(paramBlock, name);
-
-            if (index == -1)
-            {
-                return 0;
-            }
-            return paramBlock.GetFloat(index, 0);
-        }
-
-        public static float[] GetParamBlockValueColor(IIParamBlock paramBlock, string name)
-        {
-            var index = Tools.GetParamBlockIndex(paramBlock, name);
-
-            if (index == -1)
-            {
-                return null;
-            }
-            return paramBlock.GetColor(index, 0).ToArray();
-        }
-#endif
+        
         public static Vector3 ToEulerAngles(this IQuat q)
         {
             // Store the Euler angles in radians
@@ -561,46 +551,27 @@ namespace Max2Babylon
         public static void SetStringProperty(this IINode node, string propertyName, string defaultState)
         {
             string state = defaultState;
-#if MAX2015 || MAX2016 || MAX2017
             node.SetUserPropString(propertyName, state);
-#else
-            node.SetUserPropString(ref propertyName, ref state);
-#endif
         }
 
         public static bool GetBoolProperty(this IINode node, string propertyName, int defaultState = 0)
         {
             int state = defaultState;
-#if MAX2015 || MAX2016 || MAX2017
             node.GetUserPropBool(propertyName, ref state);
-#else
-            node.GetUserPropBool(ref propertyName, ref state);
-#endif
-
             return state == 1;
         }
 
         public static string GetStringProperty(this IINode node, string propertyName, string defaultState)
         {
             string state = defaultState;
-#if MAX2015 || MAX2016 || MAX2017
             node.GetUserPropString(propertyName, ref state);
-#else
-            node.GetUserPropString(ref propertyName, ref state);
-#endif
-
             return state;
         }
 
         public static float GetFloatProperty(this IINode node, string propertyName, float defaultState = 0)
         {
             float state = defaultState;
-#if MAX2015 || MAX2016 || MAX2017
             node.GetUserPropFloat(propertyName, ref state);
-#else
-            node.GetUserPropFloat(ref propertyName, ref state);
-#endif
-
             return state;
         }
 
@@ -608,28 +579,16 @@ namespace Max2Babylon
         {
             float state0 = 0;
             string name = propertyName + "_x";
-#if MAX2015 || MAX2016 || MAX2017
             node.GetUserPropFloat(name, ref state0);
-#else
-            node.GetUserPropFloat(ref name, ref state0);
-#endif
 
 
             float state1 = 0;
             name = propertyName + "_y";
-#if MAX2015 || MAX2016 || MAX2017
             node.GetUserPropFloat(name, ref state1);
-#else
-            node.GetUserPropFloat(ref name, ref state1);
-#endif
 
             float state2 = 0;
             name = propertyName + "_z";
-#if MAX2015 || MAX2016 || MAX2017
             node.GetUserPropFloat(name, ref state2);
-#else
-            node.GetUserPropFloat(ref name, ref state2);
-#endif
 
             return new[] { state0, state1, state2 };
         }
@@ -690,11 +649,7 @@ namespace Max2Babylon
         {
             if (checkBox.CheckState != CheckState.Indeterminate)
             {
-#if MAX2015 || MAX2016 || MAX2017
                 node.SetUserPropBool(propertyName, checkBox.CheckState == CheckState.Checked);
-#else
-                node.SetUserPropBool(ref propertyName, checkBox.CheckState == CheckState.Checked);
-#endif
             }
         }
 
@@ -711,11 +666,7 @@ namespace Max2Babylon
             foreach (var node in nodes)
             {
                 var value = textBox.Text;
-#if MAX2015 || MAX2016 || MAX2017
                 node.SetUserPropString(propertyName, value);
-#else
-                node.SetUserPropString(ref propertyName, ref value);
-#endif
             }
         }
 
@@ -728,11 +679,7 @@ namespace Max2Babylon
         {
             foreach (var node in nodes)
             {
-#if MAX2015 || MAX2016 || MAX2017
                 node.SetUserPropFloat(propertyName, (float)nup.Value);
-#else
-                node.SetUserPropFloat(ref propertyName, (float)nup.Value);
-#endif
             }
         }
 
@@ -746,25 +693,13 @@ namespace Max2Babylon
         public static void UpdateVector3Control(Vector3Control vector3Control, IINode node, string propertyName)
         {
             string name = propertyName + "_x";
-#if MAX2015 || MAX2016 || MAX2017
             node.SetUserPropFloat(name, vector3Control.X);
-#else
-            node.SetUserPropFloat(ref name, vector3Control.X);
-#endif
 
             name = propertyName + "_y";
-#if MAX2015 || MAX2016 || MAX2017
             node.SetUserPropFloat(name, vector3Control.Y);
-#else
-            node.SetUserPropFloat(ref name, vector3Control.Y);
-#endif
 
             name = propertyName + "_z";
-#if MAX2015 || MAX2016 || MAX2017
             node.SetUserPropFloat(name, vector3Control.Z);
-#else
-            node.SetUserPropFloat(ref name, vector3Control.Z);
-#endif
         }
 
         public static void UpdateVector3Control(Vector3Control vector3Control, List<IINode> nodes, string propertyName)
@@ -778,11 +713,7 @@ namespace Max2Babylon
         public static void UpdateComboBox(ComboBox comboBox, IINode node, string propertyName)
         {
             var value = comboBox.SelectedItem.ToString();
-#if MAX2015 || MAX2016 || MAX2017
             node.SetUserPropString(propertyName, value);
-#else
-            node.SetUserPropString(ref propertyName, ref value);
-#endif
         }
 
         public static void UpdateComboBox(ComboBox comboBox, List<IINode> nodes, string propertyName)