Quentin Rillet 8 éve
szülő
commit
f0878bc6c1
100 módosított fájl, 519969 hozzáadás és 32175 törlés
  1. 3 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonAbstractMesh.cs
  2. 98 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonColor3.cs
  3. 2 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonExport.Entities.csproj
  4. 2 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonMesh.cs
  5. 76 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonPBRMetallicRoughnessMaterial.cs
  6. 45 1
      Exporters/3ds Max/BabylonExport.Entities/BabylonQuaternion.cs
  7. 4 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonStandardMaterial.cs
  8. 7 9
      Exporters/3ds Max/BabylonExport.Entities/BabylonVector3.cs
  9. 12 2
      Exporters/3ds Max/GltfExport.Entities/GLTF.cs
  10. 4 1
      Exporters/3ds Max/GltfExport.Entities/GLTFBuffer.cs
  11. 3 1
      Exporters/3ds Max/GltfExport.Entities/GLTFBufferView.cs
  12. 2 0
      Exporters/3ds Max/GltfExport.Entities/GLTFImage.cs
  13. 2 1
      Exporters/3ds Max/GltfExport.Entities/GLTFMesh.cs
  14. BIN
      Exporters/3ds Max/Max2Babylon-0.16.0.zip
  15. 43 1
      Exporters/3ds Max/Max2Babylon.sln
  16. 31 0
      Exporters/3ds Max/Max2Babylon/2015/Max2Babylon2015.csproj
  17. 16 6
      Exporters/3ds Max/Max2Babylon/2015/Properties/Resources.Designer.cs
  18. 4 0
      Exporters/3ds Max/Max2Babylon/2015/Properties/Resources.resx
  19. BIN
      Exporters/3ds Max/Max2Babylon/2015/Resources/Logo_Exporter_v3.jpg
  20. 27 8
      Exporters/3ds Max/Max2Babylon/2017/Max2Babylon2017.csproj
  21. 20 7
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Camera.cs
  22. 64 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.AbstractMesh.cs
  23. 1 1
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Camera.cs
  24. 0 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Light.cs
  25. 493 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Material.cs
  26. 116 128
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Mesh.cs
  27. 43 27
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Texture.cs
  28. 391 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.cs
  29. 15 3
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Light.cs
  30. 173 18
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Material.cs
  31. 234 249
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Mesh.cs
  32. 324 82
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Texture.cs
  33. 214 106
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.cs
  34. 0 0
      Exporters/3ds Max/Max2Babylon/Exporter/GLTFGlobalVertex.cs
  35. 15 0
      Exporters/3ds Max/Max2Babylon/Forms/ExporterForm.Designer.cs
  36. 8 1
      Exporters/3ds Max/Max2Babylon/Forms/ExporterForm.cs
  37. 0 0
      Exporters/3ds Max/Max2Babylon/JsonTextWriterBounded.cs
  38. 47 116
      Exporters/3ds Max/Max2Babylon/Tools/Tools.cs
  39. 2 2
      Playground/frame.html
  40. 2 0
      Playground/indexStable.html
  41. 12 1
      Playground/js/frame.js
  42. 0 158
      Playground/js/perf.js
  43. 0 77
      Playground/perf.html
  44. 24 12
      Tools/Gulp/config.json
  45. 2 0
      assets/meshes/controllers/_headers
  46. 33778 0
      assets/meshes/controllers/generic/generic.babylon
  47. BIN
      assets/meshes/controllers/generic/vr_controller_01_mrhat.png
  48. BIN
      assets/meshes/controllers/generic/vr_controller_01_mrhat_MetallicGlossMap.png
  49. 21 0
      assets/meshes/controllers/microsoft/045E-065B/LICENSE
  50. BIN
      assets/meshes/controllers/microsoft/045E-065B/left.glb
  51. BIN
      assets/meshes/controllers/microsoft/045E-065B/right.glb
  52. 21 0
      assets/meshes/controllers/microsoft/default/LICENSE
  53. BIN
      assets/meshes/controllers/microsoft/default/left.glb
  54. BIN
      assets/meshes/controllers/microsoft/default/right.glb
  55. BIN
      assets/meshes/controllers/oculus/external_controller01_col.png
  56. BIN
      assets/meshes/controllers/oculus/external_controller01_col_MetallicGlossMap.png
  57. 128945 0
      assets/meshes/controllers/oculus/left.babylon
  58. 128945 0
      assets/meshes/controllers/oculus/right.babylon
  59. BIN
      assets/meshes/controllers/vive/onepointfive_texture.png
  60. BIN
      assets/meshes/controllers/vive/onepointfive_texture_MetallicGlossMap.png
  61. 170697 0
      assets/meshes/controllers/vive/wand.babylon
  62. 1829 1582
      dist/preview release/babylon.d.ts
  63. 49 48
      dist/preview release/babylon.js
  64. 2174 966
      dist/preview release/babylon.max.js
  65. 1829 1582
      dist/preview release/babylon.module.d.ts
  66. 50 49
      dist/preview release/babylon.worker.js
  67. 4368 4121
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  68. 52 37
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  69. 39564 18494
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  70. 4368 4121
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts
  71. 72 3
      dist/preview release/gui/babylon.gui.d.ts
  72. 400 17
      dist/preview release/gui/babylon.gui.js
  73. 3 3
      dist/preview release/gui/babylon.gui.min.js
  74. 72 3
      dist/preview release/gui/babylon.gui.module.d.ts
  75. 3 3
      dist/preview release/inspector/babylon.inspector.bundle.js
  76. 6 0
      dist/preview release/inspector/babylon.inspector.js
  77. 3 3
      dist/preview release/inspector/babylon.inspector.min.js
  78. 2 2
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  79. 3 3
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  80. 53 49
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  81. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  82. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  83. 53 49
      dist/preview release/loaders/babylon.glTFFileLoader.js
  84. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  85. 1 1
      dist/preview release/loaders/babylon.objFileLoader.min.js
  86. 2 1
      dist/preview release/materialsLibrary/babylon.cellMaterial.js
  87. 1 1
      dist/preview release/materialsLibrary/babylon.cellMaterial.min.js
  88. 1 1
      dist/preview release/materialsLibrary/babylon.customMaterial.min.js
  89. 2 1
      dist/preview release/materialsLibrary/babylon.fireMaterial.js
  90. 1 1
      dist/preview release/materialsLibrary/babylon.fireMaterial.min.js
  91. 2 1
      dist/preview release/materialsLibrary/babylon.furMaterial.js
  92. 1 1
      dist/preview release/materialsLibrary/babylon.furMaterial.min.js
  93. 2 1
      dist/preview release/materialsLibrary/babylon.gradientMaterial.js
  94. 1 1
      dist/preview release/materialsLibrary/babylon.gradientMaterial.min.js
  95. 2 1
      dist/preview release/materialsLibrary/babylon.lavaMaterial.js
  96. 1 1
      dist/preview release/materialsLibrary/babylon.lavaMaterial.min.js
  97. 2 1
      dist/preview release/materialsLibrary/babylon.normalMaterial.js
  98. 1 1
      dist/preview release/materialsLibrary/babylon.normalMaterial.min.js
  99. 1 1
      dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.min.js
  100. 0 0
      dist/preview release/materialsLibrary/babylon.simpleMaterial.js

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

+ 2 - 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" />
@@ -70,6 +71,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;

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

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

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

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

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


+ 43 - 1
Exporters/3ds Max/Max2Babylon.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
-VisualStudioVersion = 15.0.26430.16
+VisualStudioVersion = 15.0.26730.12
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3ds Max", "3ds Max", "{2139CC27-1C89-49C8-95AC-7715ACBADC1F}"
 EndProject
@@ -16,6 +16,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Max2Babylon2017", "Max2Baby
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GLTFExport.Entities", "GltfExport.Entities\GLTFExport.Entities.csproj", "{65686998-09AC-4A14-B23F-7FCE6BA994CF}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Max2Babylon2015", "Max2Babylon\2015\Max2Babylon2015.csproj", "{DD7C931A-8FAF-4318-BB74-71DC858CC400}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -171,11 +173,51 @@ Global
 		{65686998-09AC-4A14-B23F-7FCE6BA994CF}.Release|x64.Build.0 = Release|Any CPU
 		{65686998-09AC-4A14-B23F-7FCE6BA994CF}.Release|x86.ActiveCfg = Release|Any CPU
 		{65686998-09AC-4A14-B23F-7FCE6BA994CF}.Release|x86.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|ARM.ActiveCfg = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|ARM.Build.0 = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|Win32.Build.0 = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|x64.Build.0 = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Debug|x86.Build.0 = Debug|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|Any CPU.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|Any CPU.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|ARM.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|ARM.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|Mixed Platforms.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|Win32.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|Win32.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|x64.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|x64.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|x86.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Profile|x86.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|ARM.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|ARM.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|Win32.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|Win32.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|x64.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|x64.Build.0 = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|x86.ActiveCfg = Release|Any CPU
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
 	GlobalSection(NestedProjects) = preSolution
 		{2F49C726-A1F8-40D4-859F-1355949608DC} = {2139CC27-1C89-49C8-95AC-7715ACBADC1F}
+		{DD7C931A-8FAF-4318-BB74-71DC858CC400} = {2139CC27-1C89-49C8-95AC-7715ACBADC1F}
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {B1C70ED2-1F05-433C-A16C-59F6C5BC9A0F}
 	EndGlobalSection
 EndGlobal

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

@@ -96,6 +96,27 @@
     <Compile Include="..\Exporter\BabylonExporter.cs">
       <Link>Exporter\BabylonExporter.cs</Link>
     </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.AbstractMesh.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.AbstractMesh.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Camera.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Camera.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Light.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Light.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Material.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Material.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Mesh.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Mesh.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Texture.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Texture.cs</Link>
+    </Compile>
     <Compile Include="..\Exporter\BabylonExporter.Light.cs">
       <Link>Exporter\BabylonExporter.Light.cs</Link>
     </Compile>
@@ -117,6 +138,9 @@
     <Compile Include="..\Exporter\GlobalVertex.cs">
       <Link>Exporter\GlobalVertex.cs</Link>
     </Compile>
+    <Compile Include="..\Exporter\GLTFGlobalVertex.cs">
+      <Link>Exporter\GLTFGlobalVertex.cs</Link>
+    </Compile>
     <Compile Include="..\Forms\ActionsBuilderForm.cs">
       <Link>Forms\ActionsBuilderForm.cs</Link>
       <SubType>Form</SubType>
@@ -176,6 +200,9 @@
     <Compile Include="..\GlobalUtility.cs">
       <Link>GlobalUtility.cs</Link>
     </Compile>
+    <Compile Include="..\JsonTextWriterBounded.cs">
+      <Link>JsonTextWriterBounded.cs</Link>
+    </Compile>
     <Compile Include="..\JsonTextWriterOptimized.cs">
       <Link>JsonTextWriterOptimized.cs</Link>
     </Compile>
@@ -244,6 +271,10 @@
       <Project>{a6b76356-1d1c-4c82-8199-a6406da85a95}</Project>
       <Name>BabylonFileConverter</Name>
     </ProjectReference>
+    <ProjectReference Include="..\..\GltfExport.Entities\GLTFExport.Entities.csproj">
+      <Project>{65686998-09ac-4a14-b23f-7fce6ba994cf}</Project>
+      <Name>GLTFExport.Entities</Name>
+    </ProjectReference>
   </ItemGroup>
   <ItemGroup>
     <WCFMetadata Include="Service References\" />

+ 16 - 6
Exporters/3ds Max/Max2Babylon/2015/Properties/Resources.Designer.cs

@@ -13,13 +13,13 @@ namespace Max2Babylon.Properties {
     
     
     /// <summary>
-    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    ///   Une classe de ressource fortement typée destinée, entre autres, à la consultation des chaînes localisées.
     /// </summary>
-    // This class was auto-generated by the StronglyTypedResourceBuilder
-    // class via a tool like ResGen or Visual Studio.
-    // To add or remove a member, edit your .ResX file then rerun ResGen
-    // with the /str option, or rebuild your VS project.
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    // Cette classe a été générée automatiquement par la classe StronglyTypedResourceBuilder
+    // à l'aide d'un outil, tel que ResGen ou Visual Studio.
+    // Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen
+    // avec l'option /str ou régénérez votre projet VS.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
     internal class Resources {
@@ -59,5 +59,15 @@ namespace Max2Babylon.Properties {
                 resourceCulture = value;
             }
         }
+        
+        /// <summary>
+        ///   Recherche une ressource localisée de type System.Drawing.Bitmap.
+        /// </summary>
+        internal static System.Drawing.Bitmap Logo_Exporter_v3 {
+            get {
+                object obj = ResourceManager.GetObject("Logo_Exporter_v3", resourceCulture);
+                return ((System.Drawing.Bitmap)(obj));
+            }
+        }
     }
 }

+ 4 - 0
Exporters/3ds Max/Max2Babylon/2015/Properties/Resources.resx

@@ -117,4 +117,8 @@
   <resheader name="writer">
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
+  <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="Logo_Exporter_v3" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\Logo_Exporter_v3.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
+  </data>
 </root>

BIN
Exporters/3ds Max/Max2Babylon/2015/Resources/Logo_Exporter_v3.jpg


+ 27 - 8
Exporters/3ds Max/Max2Babylon/2017/Max2Babylon2017.csproj

@@ -96,6 +96,27 @@
     <Compile Include="..\Exporter\BabylonExporter.cs">
       <Link>Exporter\BabylonExporter.cs</Link>
     </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.AbstractMesh.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.AbstractMesh.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Camera.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Camera.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Light.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Light.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Material.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Material.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Mesh.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Mesh.cs</Link>
+    </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Texture.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Texture.cs</Link>
+    </Compile>
     <Compile Include="..\Exporter\BabylonExporter.Light.cs">
       <Link>Exporter\BabylonExporter.Light.cs</Link>
     </Compile>
@@ -117,6 +138,9 @@
     <Compile Include="..\Exporter\GlobalVertex.cs">
       <Link>Exporter\GlobalVertex.cs</Link>
     </Compile>
+    <Compile Include="..\Exporter\GLTFGlobalVertex.cs">
+      <Link>Exporter\GLTFGlobalVertex.cs</Link>
+    </Compile>
     <Compile Include="..\Forms\ActionsBuilderForm.cs">
       <Link>Forms\ActionsBuilderForm.cs</Link>
       <SubType>Form</SubType>
@@ -176,6 +200,9 @@
     <Compile Include="..\GlobalUtility.cs">
       <Link>GlobalUtility.cs</Link>
     </Compile>
+    <Compile Include="..\JsonTextWriterBounded.cs">
+      <Link>JsonTextWriterBounded.cs</Link>
+    </Compile>
     <Compile Include="..\JsonTextWriterOptimized.cs">
       <Link>JsonTextWriterOptimized.cs</Link>
     </Compile>
@@ -191,14 +218,6 @@
     <Compile Include="..\Tools\WebServer.cs">
       <Link>Tools\WebServer.cs</Link>
     </Compile>
-    <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.Texture.cs" />
-    <Compile Include="Exporter\BabylonExporter.GLTFExporter.Mesh.cs" />
-    <Compile Include="Exporter\BabylonExporter.GLTFExporter.cs" />
-    <Compile Include="Exporter\GLTFGlobalVertex.cs" />
-    <Compile Include="JsonTextWriterBounded.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
       <AutoGen>True</AutoGen>

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

+ 64 - 0
Exporters/3ds Max/Max2Babylon/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 };

Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Light.cs → Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Light.cs


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

@@ -0,0 +1,493 @@
+using BabylonExport.Entities;
+using GLTFExport.Entities;
+using System;
+using System.Drawing;
+using System.IO;
+
+namespace Max2Babylon
+{
+    partial class BabylonExporter
+    {
+        private void ExportMaterial(BabylonMaterial babylonMaterial, GLTF gltf)
+        {
+            var name = babylonMaterial.name;
+            var id = babylonMaterial.id;
+
+            RaiseMessage("GLTFExporter.Material | Export material named: " + name, 1);
+
+            if (babylonMaterial.GetType() == typeof(BabylonStandardMaterial))
+            {
+                var babylonStandardMaterial = babylonMaterial as BabylonStandardMaterial;
+
+
+                // --- 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);
+
+                // Ambient
+                for (int i = 0; i < babylonStandardMaterial.ambient.Length; i++)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.ambient[" + i + "]=" + babylonStandardMaterial.ambient[i], 3);
+                }
+
+                // Diffuse
+                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuse.Length=" + babylonStandardMaterial.diffuse.Length, 3);
+                for (int i = 0; i < babylonStandardMaterial.diffuse.Length; i++)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuse[" + i + "]=" + babylonStandardMaterial.diffuse[i], 3);
+                }
+                if (babylonStandardMaterial.diffuseTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture=null", 3);
+                }
+
+                // Normal / bump
+                if (babylonStandardMaterial.bumpTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.bumpTexture=null", 3);
+                }
+
+                // Specular
+                for (int i = 0; i < babylonStandardMaterial.specular.Length; i++)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specular[" + i + "]=" + babylonStandardMaterial.specular[i], 3);
+                }
+                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 3);
+
+                // Occlusion
+                if (babylonStandardMaterial.ambientTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.ambientTexture=null", 3);
+                }
+
+                // Emissive
+                for (int i = 0; i < babylonStandardMaterial.emissive.Length; i++)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.emissive[" + i + "]=" + babylonStandardMaterial.emissive[i], 3);
+                }
+                if (babylonStandardMaterial.emissiveTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture=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(babylonStandardMaterial, out alphaMode, out alphaCutoff);
+                gltfMaterial.alphaMode = alphaMode;
+                gltfMaterial.alphaCutoff = alphaCutoff;
+
+                // DoubleSided
+                gltfMaterial.doubleSided = !babylonMaterial.backFaceCulling;
+
+                // Normal
+                gltfMaterial.normalTexture = ExportTexture(babylonStandardMaterial.bumpTexture, gltf);
+
+                // Occulison
+                gltfMaterial.occlusionTexture = ExportTexture(babylonStandardMaterial.ambientTexture, gltf);
+
+                // Emissive
+                gltfMaterial.emissiveFactor = babylonStandardMaterial.emissive;
+                gltfMaterial.emissiveTexture = ExportTexture(babylonStandardMaterial.emissiveTexture, gltf);
+
+
+                // --------------------------------
+                // --- gltfPbrMetallicRoughness ---
+                // --------------------------------
+
+                RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2);
+                var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
+                gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;
+
+                // --- 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
+                gltfPbrMetallicRoughness.baseColorFactor = new float[4]
+                {
+                    _metallicRoughness.baseColor.r,
+                    _metallicRoughness.baseColor.g,
+                    _metallicRoughness.baseColor.b,
+                    _metallicRoughness.opacity
+                };
+
+                // Metallic roughness
+                gltfPbrMetallicRoughness.metallicFactor = _metallicRoughness.metallic;
+                gltfPbrMetallicRoughness.roughnessFactor = _metallicRoughness.roughness;
+
+
+                // --- Textures ---
+
+                if (babylonStandardMaterial.diffuseTexture != null)
+                {
+                    Func<string, Bitmap> loadTextureFromOutput = delegate (string textureName)
+                    {
+                        return LoadTexture(Path.Combine(gltf.OutputFolder, textureName));
+                    };
+
+                    Bitmap diffuseBitmap = loadTextureFromOutput(babylonStandardMaterial.diffuseTexture.name);
+
+                    if (diffuseBitmap != null)
+                    {
+                        Bitmap specularBitmap = null;
+                        if (babylonStandardMaterial.specularTexture != null)
+                        {
+                            specularBitmap = loadTextureFromOutput(babylonStandardMaterial.specularTexture.name);
+                        }
+
+                        Bitmap opacityBitmap = null;
+                        if (babylonStandardMaterial.diffuseTexture.hasAlpha == false && babylonStandardMaterial.opacityTexture != null)
+                        {
+                            opacityBitmap = loadTextureFromOutput(babylonStandardMaterial.opacityTexture.name);
+                        }
+
+                        // 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++)
+                            {
+                                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 :
+                                                 0
+                                };
+
+                                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);
+                                if (metallicRoughnessTexture.opacity != 1)
+                                {
+                                    hasAlpha = true;
+                                }
+
+                                // 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
+                        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)
+        {
+            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();
+            }
+            else
+            {
+                // glTF alpha mode default value is "OPAQUE"
+                alphaMode = null; // GLTFMaterial.AlphaMode.OPAQUE.ToString();
+            }
+            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;
+
+        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;
+        }
+    }
+}

+ 116 - 128
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,68 +89,92 @@ 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
+            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;
+                }
             }
 
             // --------------------------
@@ -395,6 +371,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);
@@ -431,53 +416,56 @@ 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 gltfNode;
+            return gltfMesh;
         }
 
         private IPoint2 createIPoint2(float[] array, int index)

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

@@ -1,18 +1,48 @@
 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.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, imageFormat);
+            }
+
+            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,11 +72,20 @@ namespace Max2Babylon
             RaiseMessage("GLTFExporter.Texture | create image", 2);
             GLTFImage gltfImage = new GLTFImage
             {
-                uri = babylonTexture.name
+                uri = name
             };
 
             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;
+            }
 
 
             // --------------------------
@@ -56,7 +95,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 +114,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;
         }
 

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

@@ -0,0 +1,391 @@
+using BabylonExport.Entities;
+using GLTFExport.Entities;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using Color = System.Drawing.Color;
+
+namespace Max2Babylon
+{
+    internal partial class BabylonExporter
+    {
+        List<BabylonMaterial> babylonMaterialsToExport;
+
+        private List<BabylonNode> babylonNodes;
+
+        public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary, bool exportGltfImagesAsBinary)
+        {
+            RaiseMessage("GLTFExporter | Export outputFile=" + outputFile + " generateBinary=" + generateBinary);
+            RaiseMessage("GLTFExporter | Exportation started", Color.Blue);
+
+            float progressionStep;
+            var progression = 0.0f;
+            ReportProgressChanged((int)progression);
+
+            // Initialization
+            initBabylonNodes(babylonScene);
+            babylonMaterialsToExport = new List<BabylonMaterial>();
+
+            var gltf = new GLTF(outputFile);
+
+            // Asset
+            gltf.asset = new GLTFAsset
+            {
+                version = "2.0",
+                generator = "Babylon2Gltf2017",
+                copyright = "2017 (c) BabylonJS"
+                // no minVersion
+            };
+
+            // Scene
+            gltf.scene = 0;
+
+            // Scenes
+            GLTFScene scene = new GLTFScene();
+            GLTFScene[] scenes = { scene };
+            gltf.scenes = scenes;
+
+            // 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);
+            progressionStep = 40.0f / babylonRootNodes.Count;
+            babylonRootNodes.ForEach(babylonNode =>
+            {
+                exportNodeRec(babylonNode, gltf, babylonScene);
+                progression += progressionStep;
+                ReportProgressChanged((int)progression);
+                CheckCancelled();
+            });
+
+            // 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 },
+                idGroupInstance = -1
+            };
+            scene.NodesList.Clear();
+            GLTFNode gltfRootNode = ExportAbstractMesh(rootNode, gltf, null);
+            gltfRootNode.ChildrenList.AddRange(tmpNodesList);
+
+            // Materials
+            RaiseMessage("GLTFExporter | Exporting materials");
+            foreach (var babylonMaterial in babylonMaterialsToExport)
+            {
+                ExportMaterial(babylonMaterial, gltf);
+                CheckCancelled();
+            };
+            RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);
+            
+            if (exportGltfImagesAsBinary)
+            {
+                SwitchImagesFromUriToBinary(gltf);
+            }
+
+            // Cast lists to arrays
+            gltf.Prepare();
+
+            // 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)))
+            {
+                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);
+                    });
+                });
+            }
+
+            // Binary
+            if (generateBinary)
+            {
+                // 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);
+        }
+
+        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;
+            var type = babylonNode.GetType();
+            if (type == typeof(BabylonAbstractMesh) ||
+                type.IsSubclassOf(typeof(BabylonAbstractMesh)))
+            {
+                gltfNode = ExportAbstractMesh(babylonNode as BabylonAbstractMesh, gltf, gltfParentNode);
+            }
+            else if (type == typeof(BabylonCamera))
+            {
+                GLTFCamera gltfCamera = ExportCamera(babylonNode as BabylonCamera, gltf, gltfParentNode);
+                gltfNode = gltfCamera.gltfNode;
+            }
+            else if (type == typeof(BabylonLight))
+            {
+                if (isNodeRelevantToExport(babylonNode, babylonScene))
+                {
+                    // Export light nodes as empty nodes (no lights in glTF 2.0 core)
+                    RaiseWarning($"GLTFExporter | Light named {babylonNode.name} has children but lights are not exported with glTF 2.0 core version. An empty node is used instead.", 1);
+                    gltfNode = ExportLight(babylonNode as BabylonLight, gltf, gltfParentNode);
+                }
+                else
+                {
+                    RaiseMessage($"GLTFExporter | Light named {babylonNode.name} is not relevant to export", 1);
+                }
+            }
+            else
+            {
+                RaiseError($"Node named {babylonNode.name} as no exporter", 1);
+            }
+
+            CheckCancelled();
+
+            // If node is exported successfully...
+            if (gltfNode != null)
+            {
+                // ...export its children
+                List<BabylonNode> babylonDescendants = getDescendants(babylonNode, babylonScene);
+                babylonDescendants.ForEach(descendant => exportNodeRec(descendant, gltf, babylonScene, gltfNode));
+            }
+        }
+
+        private List<BabylonNode> getDescendants(BabylonNode babylonNode, BabylonScene babylonScene)
+        {
+            return babylonNodes.FindAll(node => node.parentId == babylonNode.id);
+        }
+
+        /// <summary>
+        /// Return true if node descendant hierarchy has any Mesh or Camera to export
+        /// </summary>
+        private bool isNodeRelevantToExport(BabylonNode babylonNode, BabylonScene babylonScene)
+        {
+            var type = babylonNode.GetType();
+            if (type == typeof(BabylonAbstractMesh) ||
+                type.IsSubclassOf(typeof(BabylonAbstractMesh)) ||
+                type == typeof(BabylonCamera))
+            {
+                return true;
+            }
+
+            // Descandant recursivity
+            List<BabylonNode> babylonDescendants = getDescendants(babylonNode, babylonScene);
+            int indexDescendant = 0;
+            while (indexDescendant < babylonDescendants.Count) // while instead of for to stop as soon as a relevant node has been found
+            {
+                if (isNodeRelevantToExport(babylonDescendants[indexDescendant], babylonScene))
+                {
+                    return true;
+                }
+                indexDescendant++;
+            }
+
+            // 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;
+        }
+    }
+}

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

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

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

@@ -1,128 +1,130 @@
-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 +207,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 +412,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 +463,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 +683,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 };
+            });
+        }
     }
 }

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

+ 214 - 106
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.cs

@@ -2,9 +2,11 @@
 using BabylonExport.Entities;
 using Newtonsoft.Json;
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Globalization;
 using System.IO;
+using System.Runtime.InteropServices;
 using System.Text;
 using System.Threading.Tasks;
 using System.Windows.Forms;
@@ -75,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;
@@ -158,45 +160,70 @@ 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++)
             {
@@ -213,87 +240,14 @@ namespace Max2Babylon
                         babylonScene.fogColor = fog.GetColor(0).ToArray();
                         babylonScene.fogMode = 3;
                     }
-#if !MAX2015 && !MAX2016 && !MAX2017
-                    else
+                    if (babylonMainCamera != null)
                     {
-                        var paramBlock = atmospheric.GetReference(0) as IIParamBlock;
-
-                        babylonScene.fogColor = Tools.GetParamBlockValueColor(paramBlock, "Fog Color");
-                        babylonScene.fogMode = 3;
-                    }
-#endif
-                    if (mainCamera != 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)
             {
@@ -344,11 +298,165 @@ namespace Max2Babylon
             // Export glTF
             if (exportGltf)
             {
-                ExportGltf(babylonScene, outputFile, generateBinary);
+                ExportGltf(babylonScene, outputFile, generateBinary, exportGltfImagesAsBinary);
             }
 
             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;
+            }
+        }
     }
 }

Exporters/3ds Max/Max2Babylon/2017/Exporter/GLTFGlobalVertex.cs → Exporters/3ds Max/Max2Babylon/Exporter/GLTFGlobalVertex.cs


+ 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)
+        {
+
+        }
     }
 }

Exporters/3ds Max/Max2Babylon/2017/JsonTextWriterBounded.cs → Exporters/3ds Max/Max2Babylon/JsonTextWriterBounded.cs


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

+ 2 - 2
Playground/frame.html

@@ -69,7 +69,7 @@
 </head>
 
 <body>
-    <canvas touch-action="none" id="renderCanvas"></canvas>
+    <canvas touch-action="none" id="renderCanvas" tabindex="1"></canvas>
 
     <span class="label" id="fpsLabel">FPS</span>
 
@@ -77,7 +77,7 @@
     <a class="link" id="link" href="#" target="_blank">Edit</a>
 
     <script src="https://code.jquery.com/jquery.js"></script>
-    <script src="/js/frame.js"></script>
+    <script src="js/frame.js"></script>
 </body>
 
 </html>

+ 2 - 0
Playground/indexStable.html

@@ -45,6 +45,8 @@
     <script src="https://cdn.babylonjs.com/loaders/babylon.objFileLoader.js"></script>
     <script src="https://cdn.babylonjs.com/loaders/babylon.stlFileLoader.js"></script>
 
+    <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>    
+
     <script src="https://rawgit.com/BabylonJS/Extensions/master/ClonerSystem/src/babylonx.cloner.js"></script>
     <link href="css/index.css" rel="stylesheet" />
 </head>

+ 12 - 1
Playground/js/frame.js

@@ -3,8 +3,20 @@
     var currentSnippetToken;
     var engine;
     var fpsLabel = document.getElementById("fpsLabel");
+    var refreshAnchor = document.getElementById("refresh");
+    var linkAnchor = document.getElementById("link");
     var scripts;
     var zipCode;
+
+    if (location.href.toLowerCase().indexOf("noui") > -1) {
+        fpsLabel.style.visibility = "hidden";
+        fpsLabel.style.display = "none";
+        refreshAnchor.style.visibility = "hidden";
+        refreshAnchor.style.display = "none";
+        linkAnchor.style.visibility = "hidden";
+        linkAnchor.style.display = "none";
+    }
+
     BABYLON.Engine.ShadersRepository = "/src/Shaders/";
     var loadScript = function (scriptURL, title) {
         var xhr = new XMLHttpRequest();
@@ -50,7 +62,6 @@
 
             var canvas = document.getElementById("renderCanvas");
             engine = new BABYLON.Engine(canvas, true, {stencil: true});
-            engine.renderEvenInBackground = false;
             BABYLON.Camera.ForceAttachControlToAlwaysPreventDefault = true;
 
             engine.runRenderLoop(function () {

+ 0 - 158
Playground/js/perf.js

@@ -1,158 +0,0 @@
-(function () {
-    var snippetUrl = "https://babylonjs-api2.azurewebsites.net/snippets";
-    var currentSnippetToken;
-    var engine;
-    var scripts;
-    var zipCode;
-    BABYLON.Engine.ShadersRepository = "/src/Shaders/";
-    var loadScript = function (scriptURL, title) {
-        var xhr = new XMLHttpRequest();
-
-        xhr.open('GET', scriptURL, true);
-
-        xhr.onreadystatechange = function () {
-            if (xhr.readyState === 4) {
-                if (xhr.status === 200) {
-                    blockEditorChange = true;
-                    console.log(xhr.responseText);
-                    jsEditor.setValue(xhr.responseText);
-                    jsEditor.setPosition({ lineNumber: 0, column: 0 });
-                    blockEditorChange = false;
-                    compileAndRun();
-
-                    document.getElementById("currentScript").innerHTML = title;
-
-                    currentSnippetToken = null;
-                }
-            }
-        };
-
-        xhr.send(null);
-    };
-
-    var showError = function(error) {
-        console.warn(error);
-    };
-
-    compileAndRun = function (code) {
-        try {
-
-            if (!BABYLON.Engine.isSupported()) {
-                showError("Your browser does not support WebGL");
-                return;
-            }
-
-            if (engine) {
-                engine.dispose();
-                engine = null;
-            }
-
-            var canvas = document.getElementById("renderCanvas");
-            engine = new BABYLON.Engine(canvas, true, {stencil: true});
-            engine.renderEvenInBackground = false;
-            BABYLON.Camera.ForceAttachControlToAlwaysPreventDefault = true;
-
-            engine.runRenderLoop(function () {
-                if (engine.scenes.length === 0) {
-                    return;
-                }
-
-                if (canvas.width !== canvas.clientWidth) {
-                    engine.resize();
-                }
-
-                var scene = engine.scenes[0];
-
-                if (scene.activeCamera || scene.activeCameras.length > 0) {
-                    scene.render();
-                }
-            });
-
-            var scene;
-            if (code.indexOf("createScene") !== -1) { // createScene
-                eval(code);
-                scene = createScene();
-                if (!scene) {
-                    showError("createScene function must return a scene.");
-                    return;
-                }
-
-                zipCode = code + "\r\n\r\nvar scene = createScene();";
-            } else if (code.indexOf("CreateScene") !== -1) { // CreateScene
-                eval(code);
-                scene = CreateScene();
-                if (!scene) {
-                    showError("CreateScene function must return a scene.");
-                    return;
-                }
-
-                zipCode = code + "\r\n\r\nvar scene = CreateScene();";
-            } else if (code.indexOf("createscene") !== -1) { // createscene
-                eval(code);
-                scene = createscene();
-                if (!scene) {
-                    showError("createscene function must return a scene.");
-                    return;
-                }
-
-                zipCode = code + "\r\n\r\nvar scene = createscene();";
-            } else { // Direct code
-                scene = new BABYLON.Scene(engine);
-                eval("runScript = function(scene, canvas) {" + code + "}");
-                runScript(scene, canvas);
-
-                zipCode = "var scene = new BABYLON.Scene(engine);\r\n\r\n" + code;
-            }
-
-        } catch (e) {
-            // showError(e.message);
-        }
-    };
-    window.addEventListener("resize", function () {
-        if (engine) {
-            engine.resize();
-        }
-    });
-
-    // UI
-
-    var cleanHash = function () {
-        var splits = decodeURIComponent(location.hash.substr(1)).split("#");
-
-        if (splits.length > 2) {
-            splits.splice(2, splits.length - 2);
-        }
-
-        location.hash = splits.join("#");
-    };
-
-    var checkHash = function () {
-        if (location.hash) {
-            cleanHash();
-
-            try {
-                var xmlHttp = new XMLHttpRequest();
-                xmlHttp.onreadystatechange = function () {
-                    if (xmlHttp.readyState === 4) {
-                        if (xmlHttp.status === 200) {
-                            var snippetCode = JSON.parse(JSON.parse(xmlHttp.responseText)[0].jsonPayload).code;
-                            compileAndRun(snippetCode);
-                        }
-                    }
-                };
-
-                var hash = location.hash.substr(1);
-                currentSnippetToken = hash.split("#")[0];
-                if(!hash.split("#")[1]) hash += "#0";
-
-                xmlHttp.open("GET", snippetUrl + "/" + hash.replace("#", "/"));
-                xmlHttp.send();
-            } catch (e) {
-
-            }
-        }
-    };
-
-    checkHash();
-
-})();

+ 0 - 77
Playground/perf.html

@@ -1,77 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-    <title>Babylon.js Playground</title>
-    <link rel="shortcut icon" href="https://www.babylonjs.com/img/favicon/favicon.ico">
-	<link rel="apple-touch-icon" sizes="57x57" href="https://www.babylonjs.com/img/favicon/apple-icon-57x57.png">
-	<link rel="apple-touch-icon" sizes="60x60" href="https://www.babylonjs.com/img/favicon/apple-icon-60x60.png">
-	<link rel="apple-touch-icon" sizes="72x72" href="https://www.babylonjs.com/img/favicon/apple-icon-72x72.png">
-	<link rel="apple-touch-icon" sizes="76x76" href="https://www.babylonjs.com/img/favicon/apple-icon-76x76.png">
-	<link rel="apple-touch-icon" sizes="114x114" href="https://www.babylonjs.com/img/favicon/apple-icon-114x114.png">
-	<link rel="apple-touch-icon" sizes="120x120" href="https://www.babylonjs.com/img/favicon/apple-icon-120x120.png">
-	<link rel="apple-touch-icon" sizes="144x144" href="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png">
-	<link rel="apple-touch-icon" sizes="152x152" href="https://www.babylonjs.com/img/favicon/apple-icon-152x152.png">
-	<link rel="apple-touch-icon" sizes="180x180" href="https://www.babylonjs.com/img/favicon/apple-icon-180x180.png">
-	<link rel="icon" type="image/png" sizes="192x192"  href="https://www.babylonjs.com/img/favicon/android-icon-192x192.png">
-	<link rel="icon" type="image/png" sizes="32x32" href="https://www.babylonjs.com/img/favicon/favicon-32x32.png">
-	<link rel="icon" type="image/png" sizes="96x96" href="https://www.babylonjs.com/img/favicon/favicon-96x96.png">
-	<link rel="icon" type="image/png" sizes="16x16" href="https://www.babylonjs.com/img/favicon/favicon-16x16.png">
-	<link rel="manifest" href="https://www.babylonjs.com/img/favicon/manifest.json">
-	<meta name="msapplication-TileColor" content="#ffffff">
-	<meta name="msapplication-TileImage" content="https://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
-	<meta name="msapplication-config" content="https://www.babylonjs.com/img/favicon/browserconfig.xml">
-	<meta name="theme-color" content="#ffffff">
-
-    <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
-    <!-- Babylon.js -->
-    <script src="https://preview.babylonjs.com/cannon.js"></script>
-    <script src="https://preview.babylonjs.com/Oimo.js"></script>
-    <script src="https://preview.babylonjs.com/babylon.js"></script>    
-    <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
-
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.fireMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.waterMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.lavaMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.normalMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.skyMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.triPlanarMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.terrainMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.gradientMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.furMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.gridMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.shadowOnlyMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.customMaterial.min.js"></script>
-    <script src="https://preview.babylonjs.com/materialsLibrary/babylon.cellMaterial.min.js"></script>
-
-    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylon.brickProceduralTexture.min.js"></script>
-    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylon.cloudProceduralTexture.min.js"></script>
-    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylon.fireProceduralTexture.min.js"></script>
-    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylon.grassProceduralTexture.min.js"></script>
-    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylon.marbleProceduralTexture.min.js"></script>
-    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylon.roadProceduralTexture.min.js"></script>
-    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylon.starfieldProceduralTexture.min.js"></script>
-    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylon.woodProceduralTexture.min.js"></script>
-
-    <script src="https://preview.babylonjs.com/postProcessesLibrary/babylon.asciiArtPostProcess.min.js"></script>
-    <script src="https://preview.babylonjs.com/postProcessesLibrary/babylon.digitalRainPostProcess.min.js"></script>
-
-    <script src="https://preview.babylonjs.com/loaders/babylon.glTFFileLoader.js"></script>
-    <script src="https://preview.babylonjs.com/loaders/babylon.objFileLoader.js"></script>
-    <script src="https://preview.babylonjs.com/loaders/babylon.stlFileLoader.js"></script>
-
-    <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
-    
-    <script src="https://rawgit.com/BabylonJS/Extensions/master/ClonerSystem/src/babylonx.cloner.js"></script>    
-    <script src="https://rawgit.com/BabylonJS/Extensions/master/canvas2D/dist/preview%20release/babylon.canvas2d.min.js"></script>
-    <script src="https://rawgit.com/BabylonJS/Extensions/master/CompoundShader/src/babylonx.CompoundShader.js"></script>
-    <link href="frame.css" rel="stylesheet" />
-</head>
-
-<body>
-    <canvas touch-action="none" id="renderCanvas"></canvas>
-    <script src="https://code.jquery.com/jquery.js"></script>
-    <script src="/js/perf.js"></script>
-</body>
-
-</html>

+ 24 - 12
Tools/Gulp/config.json

@@ -27,16 +27,19 @@
         "minimal": ["standardMaterial", "freeCamera", "hemisphericLight"],
         "minimalWithBuilder": ["meshBuilder", "standardMaterial", "freeCamera", "hemisphericLight"],
         "minimalGLTFViewer": [
-                "animations", "arcRotateCamera", "additionalTextures", "textureFormats",
-                "shadows", "pointLight", "directionalLight", "spotLight",
-                "multiMaterial", "pbrMaterial",
-                "meshBuilder", "layer",
-                "additionalPostProcess_blur", "additionalPostProcess_fxaa", "additionalPostProcess_highlights", 
-                "additionalPostProcess_imageProcessing", "colorCurves", "defaultRenderingPipeline", "imageProcessing",
-                "debug", "textureTools", "hdr",
-                "loader",
+                "standardMaterial", "pbrMaterial", "freeCamera", "arcRotateCamera", "hemisphericLight", 
+                "pointLight", "directionalLight", "spotLight", "animations", "actions", "sprites", "picking", "collisions",
+                "particles", "solidParticles", "additionalMeshes", "meshBuilder", "audio", "additionalTextures", "shadows",
+                "loader", "userData", "offline", "fresnel", "multiMaterial", "touchCamera", "procedural", "gamepad",
+                "additionalCameras", "postProcesses", "renderingPipeline", "additionalRenderingPipeline", "defaultRenderingPipeline",
+                "depthRenderer", "geometryBufferRenderer", "additionalPostProcesses",
+                "additionalPostProcess_blur", "additionalPostProcess_fxaa", "additionalPostProcess_imageProcessing",
+                "bones", "hdr", "polygonMesh", "csg", "lensFlares", "physics", "textureFormats", "debug", "morphTargets",
+                "colorCurves", "octrees", "simd", "vr", "virtualJoystick", "optimizations", "highlights", "assetsManager",
+                "mapTexture", "dynamicFloatArray",
+                "imageProcessing", "serialization", "probes", "layer", "textureTools", "cameraBehaviors",
                 "materialsLibrary/babylon.gridMaterial.js",
-                "loaders/babylon.glTFFileLoader.js", "cameraBehaviors", "morphTargets"
+                "loaders/babylon.glTFFileLoader.js"
         ],
         "distributed": ["minimalGLTFViewer"]
     },
@@ -45,6 +48,8 @@
         "core" :
         {
             "files":[
+                "../../src/Events/babylon.keyboardEvents.js",
+                "../../src/Events/babylon.pointerEvents.js",
                 "../../src/Math/babylon.math.js",
                 "../../src/Math/babylon.math.scalar.js",
                 "../../src/babylon.mixins.js",
@@ -82,6 +87,9 @@
                 "../../src/Mesh/babylon.geometry.js",
                 "../../src/PostProcess/babylon.postProcessManager.js",
                 "../../src/Tools/babylon.performanceMonitor.js"
+            ],
+            "shaderIncludes": [
+                "depthPrePass"
             ]
         },         
         "particles" : 
@@ -167,6 +175,7 @@
         {
             "files": [
                 "../../src/Animations/babylon.animation.js",
+                "../../src/Animations/babylon.runtimeAnimation.js",
                 "../../src/Animations/babylon.animatable.js",
                 "../../src/Animations/babylon.easing.js"
             ],
@@ -536,7 +545,8 @@
                 "../../src/Gamepad/Controllers/babylon.webVRController.js",
                 "../../src/Gamepad/Controllers/babylon.oculusTouchController.js",
                 "../../src/Gamepad/Controllers/babylon.viveController.js",
-                "../../src/Gamepad/Controllers/babylon.genericController.js"
+                "../../src/Gamepad/Controllers/babylon.genericController.js",
+                "../../src/Gamepad/Controllers/babylon.windowsMotionController.js"
             ],
             "dependUpon" : [
                 "core"
@@ -585,10 +595,11 @@
                 "geometry.fragment"
             ],
             "shaderIncludes": [
+                "mrtFragmentDeclaration",
                 "bones300Declaration",
                 "instances300Declaration",
                 "instancesVertex",
-                "bonesVertex"
+                "bonesVertex"               
             ]
         },
         "postProcesses" : 
@@ -1439,7 +1450,8 @@
                     "../../gui/src/controls/image.ts",
                     "../../gui/src/controls/button.ts",
                     "../../gui/src/controls/colorPicker.ts",
-                    "../../gui/src/controls/inputText.ts"
+                    "../../gui/src/controls/inputText.ts",
+                    "../../gui/src/controls/virtualKeyboard.ts"
                 ],
                 "output": "babylon.gui.js",
                 "buildAsModule": "true",

+ 2 - 0
assets/meshes/controllers/_headers

@@ -0,0 +1,2 @@
+/*
+	Access-Control-Allow-Origin: *

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 33778 - 0
assets/meshes/controllers/generic/generic.babylon


BIN
assets/meshes/controllers/generic/vr_controller_01_mrhat.png


BIN
assets/meshes/controllers/generic/vr_controller_01_mrhat_MetallicGlossMap.png


+ 21 - 0
assets/meshes/controllers/microsoft/045E-065B/LICENSE

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright © Microsoft 2017. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

BIN
assets/meshes/controllers/microsoft/045E-065B/left.glb


BIN
assets/meshes/controllers/microsoft/045E-065B/right.glb


+ 21 - 0
assets/meshes/controllers/microsoft/default/LICENSE

@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright © Microsoft 2017. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

BIN
assets/meshes/controllers/microsoft/default/left.glb


BIN
assets/meshes/controllers/microsoft/default/right.glb


BIN
assets/meshes/controllers/oculus/external_controller01_col.png


BIN
assets/meshes/controllers/oculus/external_controller01_col_MetallicGlossMap.png


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 128945 - 0
assets/meshes/controllers/oculus/left.babylon


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 128945 - 0
assets/meshes/controllers/oculus/right.babylon


BIN
assets/meshes/controllers/vive/onepointfive_texture.png


BIN
assets/meshes/controllers/vive/onepointfive_texture_MetallicGlossMap.png


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 170697 - 0
assets/meshes/controllers/vive/wand.babylon


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1829 - 1582
dist/preview release/babylon.d.ts


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 49 - 48
dist/preview release/babylon.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2174 - 966
dist/preview release/babylon.max.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1829 - 1582
dist/preview release/babylon.module.d.ts


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 50 - 49
dist/preview release/babylon.worker.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 4368 - 4121
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 52 - 37
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 39564 - 18494
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 4368 - 4121
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts


+ 72 - 3
dist/preview release/gui/babylon.gui.d.ts

@@ -1,14 +1,21 @@
 
 declare module BABYLON.GUI {
+    interface IFocusableControl {
+        onFocus(): void;
+        onBlur(): void;
+        processKeyboard(evt: KeyboardEvent): void;
+    }
     class AdvancedDynamicTexture extends DynamicTexture {
         private _isDirty;
         private _renderObserver;
         private _resizeObserver;
+        private _preKeyboardObserver;
         private _pointerMoveObserver;
         private _pointerObserver;
-        private _canvasBlurObserver;
+        private _canvasPointerOutObserver;
         private _background;
         _rootContainer: Container;
+        _lastPickedControl: Control;
         _lastControlOver: Control;
         _lastControlDown: Control;
         _capturingControl: Control;
@@ -20,12 +27,14 @@ declare module BABYLON.GUI {
         private _idealWidth;
         private _idealHeight;
         private _renderAtIdealSize;
+        private _focusedControl;
         background: string;
         idealWidth: number;
         idealHeight: number;
         renderAtIdealSize: boolean;
         readonly layer: Layer;
         readonly rootContainer: Container;
+        focusedControl: IFocusableControl;
         constructor(name: string, width: number, height: number, scene: Scene, generateMipMaps?: boolean, samplingMode?: number);
         executeOnAllControls(func: (control: Control) => void, container?: Container): void;
         markAsDirty(): void;
@@ -39,7 +48,8 @@ declare module BABYLON.GUI {
         private _doPicking(x, y, type);
         attach(): void;
         attachToMesh(mesh: AbstractMesh, supportPointerMove?: boolean): void;
-        private _attachToOnBlur(scene);
+        private _manageFocus();
+        private _attachToOnPointerOut(scene);
         static CreateForMesh(mesh: AbstractMesh, width?: number, height?: number, supportPointerMove?: boolean): AdvancedDynamicTexture;
         static CreateFullscreenUI(name: string, foreground?: boolean, scene?: Scene): AdvancedDynamicTexture;
     }
@@ -118,6 +128,7 @@ declare module BABYLON.GUI {
         _host: AdvancedDynamicTexture;
         _currentMeasure: Measure;
         private _fontFamily;
+        private _fontStyle;
         private _fontSize;
         private _font;
         _width: ValueAndUnit;
@@ -159,6 +170,7 @@ declare module BABYLON.GUI {
         private _doNotRender;
         isHitTestVisible: boolean;
         isPointerBlocker: boolean;
+        isFocusInvisible: boolean;
         protected _linkOffsetX: ValueAndUnit;
         protected _linkOffsetY: ValueAndUnit;
         readonly typeName: string;
@@ -203,6 +215,7 @@ declare module BABYLON.GUI {
         width: string | number;
         height: string | number;
         fontFamily: string;
+        fontStyle: string;
         fontSize: string | number;
         color: string;
         zIndex: number;
@@ -249,6 +262,7 @@ declare module BABYLON.GUI {
         forcePointerUp(): void;
         _processObservables(type: number, x: number, y: number): boolean;
         private _prepareFont();
+        dispose(): void;
         private static _HORIZONTAL_ALIGNMENT_LEFT;
         private static _HORIZONTAL_ALIGNMENT_RIGHT;
         private static _HORIZONTAL_ALIGNMENT_CENTER;
@@ -300,6 +314,7 @@ declare module BABYLON.GUI {
         _processPicking(x: number, y: number, type: number): boolean;
         protected _clipForChildren(context: CanvasRenderingContext2D): void;
         protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        dispose(): void;
     }
 }
 
@@ -584,22 +599,76 @@ declare module BABYLON.GUI {
 
 
 declare module BABYLON.GUI {
-    class InputText extends Control {
+    class InputText extends Control implements IFocusableControl {
         name: string;
         private _text;
         private _background;
+        private _focusedBackground;
         private _thickness;
         private _margin;
         private _autoStretchWidth;
         private _maxWidth;
+        private _isFocused;
+        private _blinkTimeout;
+        private _blinkIsEven;
+        private _cursorOffset;
+        private _scrollLeft;
+        promptMessage: string;
+        onTextChangedObservable: Observable<InputText>;
+        onFocusObservable: Observable<InputText>;
+        onBlurObservable: Observable<InputText>;
         maxWidth: string | number;
         margin: string;
         autoStretchWidth: boolean;
         thickness: number;
+        focusedBackground: string;
         background: string;
         text: string;
         constructor(name?: string, text?: string);
+        onBlur(): void;
+        onFocus(): void;
         protected _getTypeName(): string;
+        processKey(keyCode: number, key?: string): void;
+        processKeyboard(evt: KeyboardEvent): void;
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _onPointerDown(coordinates: Vector2): boolean;
+        protected _onPointerUp(coordinates: Vector2): void;
+        dispose(): void;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    class KeyPropertySet {
+        width?: string;
+        height?: string;
+        paddingLeft?: string;
+        paddingRight?: string;
+        paddingTop?: string;
+        paddingBottom?: string;
+        color?: string;
+        background?: string;
+    }
+    class VirtualKeyboard extends StackPanel {
+        onKeyPressObservable: Observable<string>;
+        defaultButtonWidth: string;
+        defaultButtonHeight: string;
+        defaultButtonPaddingLeft: string;
+        defaultButtonPaddingRight: string;
+        defaultButtonPaddingTop: string;
+        defaultButtonPaddingBottom: string;
+        defaultButtonColor: string;
+        defaultButtonBackground: string;
+        protected _getTypeName(): string;
+        private _createKey(key, propertySet?);
+        addKeysRow(keys: Array<string>, propertySets?: Array<KeyPropertySet>): void;
+        private _connectedInputText;
+        private _onFocusObserver;
+        private _onBlurObserver;
+        private _onKeyPressObserver;
+        readonly connectedInputText: InputText;
+        connect(input: InputText): void;
+        disconnect(): void;
+        static CreateDefaultLayout(): VirtualKeyboard;
     }
 }

+ 400 - 17
dist/preview release/gui/babylon.gui.js

@@ -30,6 +30,15 @@ var BABYLON;
                 _this._idealHeight = 0;
                 _this._renderAtIdealSize = false;
                 _this._renderObserver = _this.getScene().onBeforeCameraRenderObservable.add(function (camera) { return _this._checkUpdate(camera); });
+                _this._preKeyboardObserver = _this.getScene().onPreKeyboardObservable.add(function (info) {
+                    if (!_this._focusedControl) {
+                        return;
+                    }
+                    if (info.type === BABYLON.KeyboardEventTypes.KEYDOWN) {
+                        _this._focusedControl.processKeyboard(info.event);
+                    }
+                    info.skipOnPointerObservable = true;
+                });
                 _this._rootContainer._link(null, _this);
                 _this.hasAlpha = true;
                 if (!width || !height) {
@@ -111,6 +120,25 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            Object.defineProperty(AdvancedDynamicTexture.prototype, "focusedControl", {
+                get: function () {
+                    return this._focusedControl;
+                },
+                set: function (control) {
+                    if (this._focusedControl == control) {
+                        return;
+                    }
+                    if (!this._focusedControl) {
+                        control.onFocus();
+                    }
+                    else {
+                        this._focusedControl.onBlur();
+                    }
+                    this._focusedControl = control;
+                },
+                enumerable: true,
+                configurable: true
+            });
             AdvancedDynamicTexture.prototype.executeOnAllControls = function (func, container) {
                 if (!container) {
                     container = this._rootContainer;
@@ -146,14 +174,15 @@ var BABYLON;
                 if (this._pointerObserver) {
                     this.getScene().onPointerObservable.remove(this._pointerObserver);
                 }
-                if (this._canvasBlurObserver) {
-                    this.getScene().getEngine().onCanvasBlurObservable.remove(this._canvasBlurObserver);
+                if (this._canvasPointerOutObserver) {
+                    this.getScene().getEngine().onCanvasPointerOutObservable.remove(this._canvasPointerOutObserver);
                 }
                 if (this._layerToDispose) {
                     this._layerToDispose.texture = null;
                     this._layerToDispose.dispose();
                     this._layerToDispose = null;
                 }
+                this._rootContainer.dispose();
                 _super.prototype.dispose.call(this);
             };
             AdvancedDynamicTexture.prototype._onResize = function () {
@@ -262,6 +291,7 @@ var BABYLON;
                         this._lastControlOver = null;
                     }
                 }
+                this._manageFocus();
             };
             AdvancedDynamicTexture.prototype.attach = function () {
                 var _this = this;
@@ -275,13 +305,13 @@ var BABYLON;
                     var camera = scene.cameraToUseForPointers || scene.activeCamera;
                     var engine = scene.getEngine();
                     var viewport = camera.viewport;
-                    var x = (scene.pointerX - viewport.x * engine.getRenderWidth()) / viewport.width;
-                    var y = (scene.pointerY - viewport.y * engine.getRenderHeight()) / viewport.height;
+                    var x = (scene.pointerX / engine.getHardwareScalingLevel() - viewport.x * engine.getRenderWidth()) / viewport.width;
+                    var y = (scene.pointerY / engine.getHardwareScalingLevel() - viewport.y * engine.getRenderHeight()) / viewport.height;
                     _this._shouldBlockPointer = false;
                     _this._doPicking(x, y, pi.type);
                     pi.skipOnPointerObservable = _this._shouldBlockPointer && pi.type !== BABYLON.PointerEventTypes.POINTERUP;
                 });
-                this._attachToOnBlur(scene);
+                this._attachToOnPointerOut(scene);
             };
             AdvancedDynamicTexture.prototype.attachToMesh = function (mesh, supportPointerMove) {
                 var _this = this;
@@ -303,6 +333,7 @@ var BABYLON;
                             _this._lastControlDown.forcePointerUp();
                         }
                         _this._lastControlDown = null;
+                        _this.focusedControl = null;
                     }
                     else if (pi.type === BABYLON.PointerEventTypes.POINTERMOVE) {
                         if (_this._lastControlOver) {
@@ -312,11 +343,22 @@ var BABYLON;
                     }
                 });
                 mesh.enablePointerMoveEvents = supportPointerMove;
-                this._attachToOnBlur(scene);
+                this._attachToOnPointerOut(scene);
+            };
+            AdvancedDynamicTexture.prototype._manageFocus = function () {
+                // Focus management
+                if (this._focusedControl) {
+                    if (this._focusedControl !== this._lastPickedControl) {
+                        if (this._lastPickedControl.isFocusInvisible) {
+                            return;
+                        }
+                        this.focusedControl = null;
+                    }
+                }
             };
-            AdvancedDynamicTexture.prototype._attachToOnBlur = function (scene) {
+            AdvancedDynamicTexture.prototype._attachToOnPointerOut = function (scene) {
                 var _this = this;
-                this._canvasBlurObserver = scene.getEngine().onCanvasBlurObservable.add(function () {
+                this._canvasPointerOutObserver = scene.getEngine().onCanvasPointerOutObservable.add(function () {
                     if (_this._lastControlOver) {
                         _this._lastControlOver._onPointerOut();
                     }
@@ -661,6 +703,7 @@ var BABYLON;
                 this._zIndex = 0;
                 this._currentMeasure = GUI.Measure.Empty();
                 this._fontFamily = "Arial";
+                this._fontStyle = "";
                 this._fontSize = new GUI.ValueAndUnit(18, GUI.ValueAndUnit.UNITMODE_PIXEL, false);
                 this._width = new GUI.ValueAndUnit(1, GUI.ValueAndUnit.UNITMODE_PERCENTAGE, false);
                 this._height = new GUI.ValueAndUnit(1, GUI.ValueAndUnit.UNITMODE_PERCENTAGE, false);
@@ -692,6 +735,7 @@ var BABYLON;
                 this._doNotRender = false;
                 this.isHitTestVisible = true;
                 this.isPointerBlocker = false;
+                this.isFocusInvisible = false;
                 this._linkOffsetX = new GUI.ValueAndUnit(0);
                 this._linkOffsetY = new GUI.ValueAndUnit(0);
                 /**
@@ -895,6 +939,20 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            Object.defineProperty(Control.prototype, "fontStyle", {
+                get: function () {
+                    return this._fontStyle;
+                },
+                set: function (value) {
+                    if (this._fontStyle === value) {
+                        return;
+                    }
+                    this._fontStyle = value;
+                    this._fontSet = true;
+                },
+                enumerable: true,
+                configurable: true
+            });
             Object.defineProperty(Control.prototype, "fontSize", {
                 get: function () {
                     return this._fontSize.toString(this._host);
@@ -1429,6 +1487,7 @@ var BABYLON;
                 if (type === BABYLON.PointerEventTypes.POINTERDOWN) {
                     this._onPointerDown(this._dummyVector2);
                     this._host._lastControlDown = this;
+                    this._host._lastPickedControl = this;
                     return true;
                 }
                 if (type === BABYLON.PointerEventTypes.POINTERUP) {
@@ -1444,9 +1503,17 @@ var BABYLON;
                 if (!this._font && !this._fontSet) {
                     return;
                 }
-                this._font = this._fontSize.getValue(this._host) + "px " + this._fontFamily;
+                this._font = this._fontStyle + " " + this._fontSize.getValue(this._host) + "px " + this._fontFamily;
                 this._fontOffset = Control._GetFontOffset(this._font);
             };
+            Control.prototype.dispose = function () {
+                this.onDirtyObservable.clear();
+                this.onPointerDownObservable.clear();
+                this.onPointerEnterObservable.clear();
+                this.onPointerMoveObservable.clear();
+                this.onPointerOutObservable.clear();
+                this.onPointerUpObservable.clear();
+            };
             Object.defineProperty(Control, "HORIZONTAL_ALIGNMENT_LEFT", {
                 get: function () {
                     return Control._HORIZONTAL_ALIGNMENT_LEFT;
@@ -1646,6 +1713,7 @@ var BABYLON;
                     return this;
                 }
                 control._link(this, this._host);
+                control._markAllAsDirty();
                 this._reOrderControl(control);
                 this._markAsDirty();
                 return this;
@@ -1735,6 +1803,13 @@ var BABYLON;
                 _super.prototype._additionalProcessing.call(this, parentMeasure, context);
                 this._measureForChildren.copyFrom(this._currentMeasure);
             };
+            Container.prototype.dispose = function () {
+                _super.prototype.dispose.call(this);
+                for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
+                    var control = _a[_i];
+                    control.dispose();
+                }
+            };
             return Container;
         }(GUI.Control));
         GUI.Container = Container;
@@ -3719,10 +3794,18 @@ var BABYLON;
                 _this.name = name;
                 _this._text = "";
                 _this._background = "black";
+                _this._focusedBackground = "black";
                 _this._thickness = 1;
                 _this._margin = new GUI.ValueAndUnit(10, GUI.ValueAndUnit.UNITMODE_PIXEL);
                 _this._autoStretchWidth = true;
                 _this._maxWidth = new GUI.ValueAndUnit(1, GUI.ValueAndUnit.UNITMODE_PERCENTAGE, false);
+                _this._isFocused = false;
+                _this._blinkIsEven = false;
+                _this._cursorOffset = 0;
+                _this.promptMessage = "Please enter text:";
+                _this.onTextChangedObservable = new BABYLON.Observable();
+                _this.onFocusObservable = new BABYLON.Observable();
+                _this.onBlurObservable = new BABYLON.Observable();
                 _this.text = text;
                 return _this;
             }
@@ -3784,6 +3867,20 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            Object.defineProperty(InputText.prototype, "focusedBackground", {
+                get: function () {
+                    return this._focusedBackground;
+                },
+                set: function (value) {
+                    if (this._focusedBackground === value) {
+                        return;
+                    }
+                    this._focusedBackground = value;
+                    this._markAsDirty();
+                },
+                enumerable: true,
+                configurable: true
+            });
             Object.defineProperty(InputText.prototype, "background", {
                 get: function () {
                     return this._background;
@@ -3808,33 +3905,178 @@ var BABYLON;
                     }
                     this._text = value;
                     this._markAsDirty();
+                    this.onTextChangedObservable.notifyObservers(this);
                 },
                 enumerable: true,
                 configurable: true
             });
+            InputText.prototype.onBlur = function () {
+                this._isFocused = false;
+                this._scrollLeft = null;
+                this._cursorOffset = 0;
+                clearTimeout(this._blinkTimeout);
+                this._markAsDirty();
+                this.onBlurObservable.notifyObservers(this);
+            };
+            InputText.prototype.onFocus = function () {
+                this._scrollLeft = null;
+                this._isFocused = true;
+                this._blinkIsEven = false;
+                this._cursorOffset = 0;
+                this._markAsDirty();
+                this.onFocusObservable.notifyObservers(this);
+                if (navigator.userAgent.indexOf("Mobile") !== -1) {
+                    this.text = prompt(this.promptMessage);
+                    this._host.focusedControl = null;
+                    return;
+                }
+            };
             InputText.prototype._getTypeName = function () {
                 return "InputText";
             };
+            InputText.prototype.processKey = function (keyCode, key) {
+                // Specific cases
+                switch (keyCode) {
+                    case 8:// BACKSPACE
+                        if (this._text && this._text.length > 0) {
+                            if (this._cursorOffset === 0) {
+                                this.text = this._text.substr(0, this._text.length - 1);
+                            }
+                            else {
+                                var deletePosition = this._text.length - this._cursorOffset;
+                                if (deletePosition > 0) {
+                                    this.text = this._text.slice(0, deletePosition - 1) + this._text.slice(deletePosition);
+                                }
+                            }
+                        }
+                        return;
+                    case 46:// DELETE
+                        if (this._text && this._text.length > 0) {
+                            var deletePosition = this._text.length - this._cursorOffset;
+                            this.text = this._text.slice(0, deletePosition) + this._text.slice(deletePosition + 1);
+                            this._cursorOffset--;
+                        }
+                        return;
+                    case 13:// RETURN
+                        this._host.focusedControl = null;
+                        return;
+                    case 35:// END
+                        this._cursorOffset = 0;
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                    case 36:// HOME
+                        this._cursorOffset = this._text.length;
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                    case 37:// LEFT
+                        this._cursorOffset++;
+                        if (this._cursorOffset > this._text.length) {
+                            this._cursorOffset = this._text.length;
+                        }
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                    case 39:// RIGHT
+                        this._cursorOffset--;
+                        if (this._cursorOffset < 0) {
+                            this._cursorOffset = 0;
+                        }
+                        this._blinkIsEven = false;
+                        this._markAsDirty();
+                        return;
+                }
+                // Printable characters
+                if ((keyCode === -1) ||
+                    (keyCode === 32) ||
+                    (keyCode > 47 && keyCode < 58) ||
+                    (keyCode > 64 && keyCode < 91) ||
+                    (keyCode > 185 && keyCode < 193) ||
+                    (keyCode > 218 && keyCode < 223) ||
+                    (keyCode > 95 && keyCode < 112)) {
+                    if (this._cursorOffset === 0) {
+                        this.text += key;
+                    }
+                    else {
+                        var insertPosition = this._text.length - this._cursorOffset;
+                        this.text = this._text.slice(0, insertPosition) + key + this._text.slice(insertPosition);
+                    }
+                }
+            };
+            InputText.prototype.processKeyboard = function (evt) {
+                this.processKey(evt.keyCode, evt.key);
+            };
             InputText.prototype._draw = function (parentMeasure, context) {
+                var _this = this;
                 context.save();
                 this._applyStates(context);
                 if (this._processMeasures(parentMeasure, context)) {
                     // Background
-                    if (this._background) {
+                    if (this._isFocused) {
+                        if (this._focusedBackground) {
+                            context.fillStyle = this._focusedBackground;
+                            context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
+                        }
+                    }
+                    else if (this._background) {
                         context.fillStyle = this._background;
                         context.fillRect(this._currentMeasure.left, this._currentMeasure.top, this._currentMeasure.width, this._currentMeasure.height);
                     }
+                    if (!this._fontOffset) {
+                        this._fontOffset = GUI.Control._GetFontOffset(context.font);
+                    }
                     // Text
-                    if (this._text) {
-                        if (this.color) {
-                            context.fillStyle = this.color;
+                    var clipTextLeft = this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width);
+                    if (this.color) {
+                        context.fillStyle = this.color;
+                    }
+                    var textWidth = context.measureText(this._text).width;
+                    var marginWidth = this._margin.getValueInPixel(this._host, parentMeasure.width) * 2;
+                    if (this._autoStretchWidth) {
+                        this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), textWidth + marginWidth) + "px";
+                    }
+                    var rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
+                    var availableWidth = this._width.getValueInPixel(this._host, parentMeasure.width) - marginWidth;
+                    context.save();
+                    context.beginPath();
+                    context.rect(clipTextLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, availableWidth + 2, this._currentMeasure.height);
+                    context.clip();
+                    if (this._isFocused && textWidth > availableWidth) {
+                        var textLeft = clipTextLeft - textWidth + availableWidth;
+                        if (!this._scrollLeft) {
+                            this._scrollLeft = textLeft;
                         }
-                        var rootY = this._fontOffset.ascent + (this._currentMeasure.height - this._fontOffset.height) / 2;
-                        context.fillText(this._text, this._currentMeasure.left + this._margin.getValueInPixel(this._host, parentMeasure.width), this._currentMeasure.top + rootY);
-                        if (this._autoStretchWidth) {
-                            this.width = Math.min(this._maxWidth.getValueInPixel(this._host, parentMeasure.width), context.measureText(this._text).width + this._margin.getValueInPixel(this._host, parentMeasure.width) * 2) + "px";
+                    }
+                    else {
+                        this._scrollLeft = clipTextLeft;
+                    }
+                    context.fillText(this._text, this._scrollLeft, this._currentMeasure.top + rootY);
+                    // Cursor
+                    if (this._isFocused) {
+                        if (!this._blinkIsEven) {
+                            var cursorOffsetText = this.text.substr(this._text.length - this._cursorOffset);
+                            var cursorOffsetWidth = context.measureText(cursorOffsetText).width;
+                            var cursorLeft = this._scrollLeft + textWidth - cursorOffsetWidth;
+                            if (cursorLeft < clipTextLeft) {
+                                this._scrollLeft += (clipTextLeft - cursorLeft);
+                                cursorLeft = clipTextLeft;
+                                this._markAsDirty();
+                            }
+                            else if (cursorLeft > clipTextLeft + availableWidth) {
+                                this._scrollLeft += (clipTextLeft + availableWidth - cursorLeft);
+                                cursorLeft = clipTextLeft + availableWidth;
+                                this._markAsDirty();
+                            }
+                            context.fillRect(cursorLeft, this._currentMeasure.top + (this._currentMeasure.height - this._fontOffset.height) / 2, 2, this._fontOffset.height);
                         }
+                        clearTimeout(this._blinkTimeout);
+                        this._blinkTimeout = setTimeout(function () {
+                            _this._blinkIsEven = !_this._blinkIsEven;
+                            _this._markAsDirty();
+                        }, 500);
                     }
+                    context.restore();
                     // Border
                     if (this._thickness) {
                         if (this.color) {
@@ -3846,8 +4088,149 @@ var BABYLON;
                 }
                 context.restore();
             };
+            InputText.prototype._onPointerDown = function (coordinates) {
+                if (!_super.prototype._onPointerDown.call(this, coordinates)) {
+                    return false;
+                }
+                this._host.focusedControl = this;
+                return true;
+            };
+            InputText.prototype._onPointerUp = function (coordinates) {
+                _super.prototype._onPointerUp.call(this, coordinates);
+            };
+            InputText.prototype.dispose = function () {
+                _super.prototype.dispose.call(this);
+                this.onBlurObservable.clear();
+                this.onFocusObservable.clear();
+                this.onTextChangedObservable.clear();
+            };
             return InputText;
         }(GUI.Control));
         GUI.InputText = InputText;
     })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
 })(BABYLON || (BABYLON = {}));
+
+/// <reference path="../../../dist/preview release/babylon.d.ts"/>
+var __extends = (this && this.__extends) || (function () {
+    var extendStatics = Object.setPrototypeOf ||
+        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+    return function (d, b) {
+        extendStatics(d, b);
+        function __() { this.constructor = d; }
+        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+    };
+})();
+var BABYLON;
+(function (BABYLON) {
+    var GUI;
+    (function (GUI) {
+        var KeyPropertySet = (function () {
+            function KeyPropertySet() {
+            }
+            return KeyPropertySet;
+        }());
+        GUI.KeyPropertySet = KeyPropertySet;
+        var VirtualKeyboard = (function (_super) {
+            __extends(VirtualKeyboard, _super);
+            function VirtualKeyboard() {
+                var _this = _super !== null && _super.apply(this, arguments) || this;
+                _this.onKeyPressObservable = new BABYLON.Observable();
+                _this.defaultButtonWidth = "40px";
+                _this.defaultButtonHeight = "40px";
+                _this.defaultButtonPaddingLeft = "2px";
+                _this.defaultButtonPaddingRight = "2px";
+                _this.defaultButtonPaddingTop = "2px";
+                _this.defaultButtonPaddingBottom = "2px";
+                _this.defaultButtonColor = "#DDD";
+                _this.defaultButtonBackground = "#070707";
+                return _this;
+            }
+            VirtualKeyboard.prototype._getTypeName = function () {
+                return "VirtualKeyboard";
+            };
+            VirtualKeyboard.prototype._createKey = function (key, propertySet) {
+                var _this = this;
+                var button = GUI.Button.CreateSimpleButton(key, key);
+                button.width = propertySet && propertySet.width ? propertySet.width : this.defaultButtonWidth;
+                button.height = propertySet && propertySet.height ? propertySet.height : this.defaultButtonHeight;
+                button.color = propertySet && propertySet.color ? propertySet.color : this.defaultButtonColor;
+                button.background = propertySet && propertySet.background ? propertySet.background : this.defaultButtonBackground;
+                button.paddingLeft = propertySet && propertySet.paddingLeft ? propertySet.paddingLeft : this.defaultButtonPaddingLeft;
+                button.paddingRight = propertySet && propertySet.paddingRight ? propertySet.paddingRight : this.defaultButtonPaddingRight;
+                button.paddingTop = propertySet && propertySet.paddingTop ? propertySet.paddingTop : this.defaultButtonPaddingTop;
+                button.paddingBottom = propertySet && propertySet.paddingBottom ? propertySet.paddingBottom : this.defaultButtonPaddingBottom;
+                button.thickness = 0;
+                button.isFocusInvisible = true;
+                button.onPointerUpObservable.add(function () {
+                    _this.onKeyPressObservable.notifyObservers(key);
+                });
+                return button;
+            };
+            VirtualKeyboard.prototype.addKeysRow = function (keys, propertySets) {
+                var panel = new GUI.StackPanel();
+                panel.isVertical = false;
+                panel.isFocusInvisible = true;
+                for (var i = 0; i < keys.length; i++) {
+                    var properties = null;
+                    if (propertySets && propertySets.length === keys.length) {
+                        properties = propertySets[i];
+                    }
+                    panel.addControl(this._createKey(keys[i], properties));
+                }
+                this.addControl(panel);
+            };
+            Object.defineProperty(VirtualKeyboard.prototype, "connectedInputText", {
+                get: function () {
+                    return this._connectedInputText;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            VirtualKeyboard.prototype.connect = function (input) {
+                var _this = this;
+                this.isVisible = false;
+                this._connectedInputText = input;
+                // Events hooking
+                this._onFocusObserver = input.onFocusObservable.add(function () {
+                    _this.isVisible = true;
+                });
+                this._onBlurObserver = input.onBlurObservable.add(function () {
+                    _this.isVisible = false;
+                });
+                this._onKeyPressObserver = this.onKeyPressObservable.add(function (key) {
+                    switch (key) {
+                        case "\u2190":
+                            _this._connectedInputText.processKey(8);
+                            return;
+                        case "\u21B5":
+                            _this._connectedInputText.processKey(13);
+                            return;
+                    }
+                    _this._connectedInputText.processKey(-1, key);
+                });
+            };
+            VirtualKeyboard.prototype.disconnect = function () {
+                if (!this._connectedInputText) {
+                    return;
+                }
+                this._connectedInputText.onFocusObservable.remove(this._onFocusObserver);
+                this._connectedInputText.onBlurObservable.remove(this._onBlurObserver);
+                this.onKeyPressObservable.remove(this._onKeyPressObserver);
+                this._connectedInputText = null;
+            };
+            // Statics
+            VirtualKeyboard.CreateDefaultLayout = function () {
+                var returnValue = new VirtualKeyboard();
+                returnValue.addKeysRow(["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "\u2190"]);
+                returnValue.addKeysRow(["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]);
+                returnValue.addKeysRow(["a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", "\u21B5"]);
+                returnValue.addKeysRow(["z", "x", "c", "v", "b", "n", "m", ",", ".", "/"]);
+                returnValue.addKeysRow([" "], [{ width: "200px" }]);
+                return returnValue;
+            };
+            return VirtualKeyboard;
+        }(GUI.StackPanel));
+        GUI.VirtualKeyboard = VirtualKeyboard;
+    })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
+})(BABYLON || (BABYLON = {}));

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 3 - 3
dist/preview release/gui/babylon.gui.min.js


+ 72 - 3
dist/preview release/gui/babylon.gui.module.d.ts

@@ -1,14 +1,21 @@
 /// <reference path="../../dist/preview release/babylon.d.ts" />
 declare module BABYLON.GUI {
+    interface IFocusableControl {
+        onFocus(): void;
+        onBlur(): void;
+        processKeyboard(evt: KeyboardEvent): void;
+    }
     class AdvancedDynamicTexture extends DynamicTexture {
         private _isDirty;
         private _renderObserver;
         private _resizeObserver;
+        private _preKeyboardObserver;
         private _pointerMoveObserver;
         private _pointerObserver;
-        private _canvasBlurObserver;
+        private _canvasPointerOutObserver;
         private _background;
         _rootContainer: Container;
+        _lastPickedControl: Control;
         _lastControlOver: Control;
         _lastControlDown: Control;
         _capturingControl: Control;
@@ -20,12 +27,14 @@ declare module BABYLON.GUI {
         private _idealWidth;
         private _idealHeight;
         private _renderAtIdealSize;
+        private _focusedControl;
         background: string;
         idealWidth: number;
         idealHeight: number;
         renderAtIdealSize: boolean;
         readonly layer: Layer;
         readonly rootContainer: Container;
+        focusedControl: IFocusableControl;
         constructor(name: string, width: number, height: number, scene: Scene, generateMipMaps?: boolean, samplingMode?: number);
         executeOnAllControls(func: (control: Control) => void, container?: Container): void;
         markAsDirty(): void;
@@ -39,7 +48,8 @@ declare module BABYLON.GUI {
         private _doPicking(x, y, type);
         attach(): void;
         attachToMesh(mesh: AbstractMesh, supportPointerMove?: boolean): void;
-        private _attachToOnBlur(scene);
+        private _manageFocus();
+        private _attachToOnPointerOut(scene);
         static CreateForMesh(mesh: AbstractMesh, width?: number, height?: number, supportPointerMove?: boolean): AdvancedDynamicTexture;
         static CreateFullscreenUI(name: string, foreground?: boolean, scene?: Scene): AdvancedDynamicTexture;
     }
@@ -118,6 +128,7 @@ declare module BABYLON.GUI {
         _host: AdvancedDynamicTexture;
         _currentMeasure: Measure;
         private _fontFamily;
+        private _fontStyle;
         private _fontSize;
         private _font;
         _width: ValueAndUnit;
@@ -159,6 +170,7 @@ declare module BABYLON.GUI {
         private _doNotRender;
         isHitTestVisible: boolean;
         isPointerBlocker: boolean;
+        isFocusInvisible: boolean;
         protected _linkOffsetX: ValueAndUnit;
         protected _linkOffsetY: ValueAndUnit;
         readonly typeName: string;
@@ -203,6 +215,7 @@ declare module BABYLON.GUI {
         width: string | number;
         height: string | number;
         fontFamily: string;
+        fontStyle: string;
         fontSize: string | number;
         color: string;
         zIndex: number;
@@ -249,6 +262,7 @@ declare module BABYLON.GUI {
         forcePointerUp(): void;
         _processObservables(type: number, x: number, y: number): boolean;
         private _prepareFont();
+        dispose(): void;
         private static _HORIZONTAL_ALIGNMENT_LEFT;
         private static _HORIZONTAL_ALIGNMENT_RIGHT;
         private static _HORIZONTAL_ALIGNMENT_CENTER;
@@ -300,6 +314,7 @@ declare module BABYLON.GUI {
         _processPicking(x: number, y: number, type: number): boolean;
         protected _clipForChildren(context: CanvasRenderingContext2D): void;
         protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        dispose(): void;
     }
 }
 
@@ -584,23 +599,77 @@ declare module BABYLON.GUI {
 
 /// <reference path="../../../dist/preview release/babylon.d.ts" />
 declare module BABYLON.GUI {
-    class InputText extends Control {
+    class InputText extends Control implements IFocusableControl {
         name: string;
         private _text;
         private _background;
+        private _focusedBackground;
         private _thickness;
         private _margin;
         private _autoStretchWidth;
         private _maxWidth;
+        private _isFocused;
+        private _blinkTimeout;
+        private _blinkIsEven;
+        private _cursorOffset;
+        private _scrollLeft;
+        promptMessage: string;
+        onTextChangedObservable: Observable<InputText>;
+        onFocusObservable: Observable<InputText>;
+        onBlurObservable: Observable<InputText>;
         maxWidth: string | number;
         margin: string;
         autoStretchWidth: boolean;
         thickness: number;
+        focusedBackground: string;
         background: string;
         text: string;
         constructor(name?: string, text?: string);
+        onBlur(): void;
+        onFocus(): void;
         protected _getTypeName(): string;
+        processKey(keyCode: number, key?: string): void;
+        processKeyboard(evt: KeyboardEvent): void;
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _onPointerDown(coordinates: Vector2): boolean;
+        protected _onPointerUp(coordinates: Vector2): void;
+        dispose(): void;
+    }
+}
+
+/// <reference path="../../../dist/preview release/babylon.d.ts" />
+declare module BABYLON.GUI {
+    class KeyPropertySet {
+        width?: string;
+        height?: string;
+        paddingLeft?: string;
+        paddingRight?: string;
+        paddingTop?: string;
+        paddingBottom?: string;
+        color?: string;
+        background?: string;
+    }
+    class VirtualKeyboard extends StackPanel {
+        onKeyPressObservable: Observable<string>;
+        defaultButtonWidth: string;
+        defaultButtonHeight: string;
+        defaultButtonPaddingLeft: string;
+        defaultButtonPaddingRight: string;
+        defaultButtonPaddingTop: string;
+        defaultButtonPaddingBottom: string;
+        defaultButtonColor: string;
+        defaultButtonBackground: string;
+        protected _getTypeName(): string;
+        private _createKey(key, propertySet?);
+        addKeysRow(keys: Array<string>, propertySets?: Array<KeyPropertySet>): void;
+        private _connectedInputText;
+        private _onFocusObserver;
+        private _onBlurObserver;
+        private _onKeyPressObserver;
+        readonly connectedInputText: InputText;
+        connect(input: InputText): void;
+        disconnect(): void;
+        static CreateDefaultLayout(): VirtualKeyboard;
     }
 }
 

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 3 - 3
dist/preview release/inspector/babylon.inspector.bundle.js


+ 6 - 0
dist/preview release/inspector/babylon.inspector.js

@@ -3689,6 +3689,12 @@ var INSPECTOR;
                     elem: elemValue,
                     updateFct: function () { return BABYLON.Tools.Format(_this._scene.getLastFrameDuration()); }
                 });
+                elemLabel = _this._createStatLabel("Inter-frame", _this._panel);
+                elemValue = INSPECTOR.Helpers.CreateDiv('stat-value', _this._panel);
+                _this._updatableProperties.push({
+                    elem: elemValue,
+                    updateFct: function () { return BABYLON.Tools.Format(_this._scene.getInterFramePerfCounter()); }
+                });
                 elemLabel = _this._createStatLabel("Potential FPS", _this._panel);
                 elemValue = INSPECTOR.Helpers.CreateDiv('stat-value', _this._panel);
                 _this._updatableProperties.push({

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 3 - 3
dist/preview release/inspector/babylon.inspector.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2 - 2
dist/preview release/loaders/babylon.glTF1FileLoader.min.js


+ 3 - 3
dist/preview release/loaders/babylon.glTF2FileLoader.d.ts

@@ -258,7 +258,8 @@ declare module BABYLON.GLTF2 {
     interface IGLTFTexture extends IGLTFChildRootProperty {
         sampler?: number;
         source: number;
-        babylonTextures?: Texture[];
+        url?: string;
+        dataReadyObservable?: Observable<IGLTFTexture>;
     }
     interface IGLTFTextureInfo {
         index: number;
@@ -273,7 +274,6 @@ declare module BABYLON.GLTF2 {
         cameras?: IGLTFCamera[];
         extensionsUsed?: string[];
         extensionsRequired?: string[];
-        glExtensionsUsed?: string[];
         images?: IGLTFImage[];
         materials?: IGLTFMaterial[];
         meshes?: IGLTFMesh[];
@@ -299,9 +299,9 @@ declare module BABYLON.GLTF2 {
         private _errorCallback;
         private _renderReady;
         private _disposed;
-        private _objectURLs;
         private _blockPendingTracking;
         private _nonBlockingData;
+        private _rootMesh;
         private _renderReadyObservable;
         private _renderPendingCount;
         private _loaderPendingCount;

+ 53 - 49
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -318,7 +318,6 @@ var BABYLON;
             function GLTFLoader(parent) {
                 this._renderReady = false;
                 this._disposed = false;
-                this._objectURLs = new Array();
                 this._blockPendingTracking = false;
                 // Observable with boolean indicating success or error.
                 this._renderReadyObservable = new BABYLON.Observable();
@@ -365,8 +364,13 @@ var BABYLON;
                 }
                 this._disposed = true;
                 // Revoke object urls created during load
-                this._objectURLs.forEach(function (url) { return URL.revokeObjectURL(url); });
-                this._objectURLs.length = 0;
+                if (this._gltf.textures) {
+                    this._gltf.textures.forEach(function (texture) {
+                        if (texture.url) {
+                            URL.revokeObjectURL(texture.url);
+                        }
+                    });
+                }
                 this._gltf = undefined;
                 this._babylonScene = undefined;
                 this._rootUrl = undefined;
@@ -442,7 +446,7 @@ var BABYLON;
                 this._gltf = data.json;
                 var binaryBuffer;
                 var buffers = this._gltf.buffers;
-                if (buffers.length > 0 && buffers[0].uri === undefined) {
+                if (buffers && buffers[0].uri === undefined) {
                     binaryBuffer = buffers[0];
                 }
                 if (data.bin) {
@@ -458,19 +462,25 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._addRightHandToLeftHandRootTransform = function () {
-                var rootMesh = new BABYLON.Mesh("root", this._babylonScene);
-                rootMesh.scaling = new BABYLON.Vector3(1, 1, -1);
-                rootMesh.rotation.y = Math.PI;
+                this._rootMesh = new BABYLON.Mesh("root", this._babylonScene);
+                this._rootMesh.isVisible = false;
+                this._rootMesh.scaling = new BABYLON.Vector3(1, 1, -1);
+                this._rootMesh.rotation.y = Math.PI;
                 var nodes = this._gltf.nodes;
-                for (var i = 0; i < nodes.length; i++) {
-                    var mesh = nodes[i].babylonMesh;
-                    if (mesh && !mesh.parent) {
-                        mesh.parent = rootMesh;
+                if (nodes) {
+                    for (var i = 0; i < nodes.length; i++) {
+                        var mesh = nodes[i].babylonMesh;
+                        if (mesh && !mesh.parent) {
+                            mesh.parent = this._rootMesh;
+                        }
                     }
                 }
             };
             GLTFLoader.prototype._getMeshes = function () {
                 var meshes = [];
+                if (this._rootMesh) {
+                    meshes.push(this._rootMesh);
+                }
                 var nodes = this._gltf.nodes;
                 if (nodes) {
                     nodes.forEach(function (node) {
@@ -1286,6 +1296,7 @@ var BABYLON;
                         BABYLON.Tools.Warn("Invalid alpha mode '" + material.alphaMode + "'");
                         break;
                 }
+                babylonMaterial.alphaCutOff = material.alphaCutoff === undefined ? 0.5 : material.alphaCutoff;
             };
             GLTFLoader.prototype.loadTexture = function (textureInfo) {
                 var _this = this;
@@ -1294,28 +1305,12 @@ var BABYLON;
                 if (!texture || texture.source === undefined) {
                     return null;
                 }
-                // check the cache first
-                var babylonTexture;
-                if (texture.babylonTextures) {
-                    babylonTexture = texture.babylonTextures[texCoord];
-                    if (!babylonTexture) {
-                        for (var i = 0; i < texture.babylonTextures.length; i++) {
-                            babylonTexture = texture.babylonTextures[i];
-                            if (babylonTexture) {
-                                babylonTexture = babylonTexture.clone();
-                                babylonTexture.coordinatesIndex = texCoord;
-                                break;
-                            }
-                        }
-                    }
-                    return babylonTexture;
-                }
                 var source = this._gltf.images[texture.source];
                 var sampler = (texture.sampler === undefined ? {} : this._gltf.samplers[texture.sampler]);
                 var noMipMaps = (sampler.minFilter === GLTF2.ETextureMinFilter.NEAREST || sampler.minFilter === GLTF2.ETextureMinFilter.LINEAR);
                 var samplingMode = GLTF2.GLTFUtils.GetTextureSamplingMode(sampler.magFilter, sampler.minFilter);
                 this.addPendingData(texture);
-                babylonTexture = new BABYLON.Texture(null, this._babylonScene, noMipMaps, false, samplingMode, function () {
+                var babylonTexture = new BABYLON.Texture(null, this._babylonScene, noMipMaps, false, samplingMode, function () {
                     if (!_this._disposed) {
                         _this.removePendingData(texture);
                     }
@@ -1325,34 +1320,44 @@ var BABYLON;
                         _this.removePendingData(texture);
                     }
                 });
-                var setTextureData = function (data) {
-                    var url = URL.createObjectURL(new Blob([data], { type: source.mimeType }));
-                    _this._objectURLs.push(url);
-                    babylonTexture.updateURL(url);
-                };
-                if (!source.uri) {
-                    var bufferView = this._gltf.bufferViews[source.bufferView];
-                    this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, 1, GLTF2.EComponentType.UNSIGNED_BYTE, setTextureData);
+                if (texture.url) {
+                    babylonTexture.updateURL(texture.url);
                 }
-                else if (GLTF2.GLTFUtils.IsBase64(source.uri)) {
-                    setTextureData(new Uint8Array(GLTF2.GLTFUtils.DecodeBase64(source.uri)));
+                else if (texture.dataReadyObservable) {
+                    texture.dataReadyObservable.add(function (texture) {
+                        babylonTexture.updateURL(texture.url);
+                    });
                 }
                 else {
-                    BABYLON.Tools.LoadFile(this._rootUrl + source.uri, setTextureData, function (event) {
-                        if (!_this._disposed) {
-                            _this._onProgress(event);
-                        }
-                    }, this._babylonScene.database, true, function (request) {
-                        _this._onError("Failed to load file '" + source.uri + "': " + request.status + " " + request.statusText);
+                    texture.dataReadyObservable = new BABYLON.Observable();
+                    texture.dataReadyObservable.add(function (texture) {
+                        babylonTexture.updateURL(texture.url);
                     });
+                    var setTextureData = function (data) {
+                        texture.url = URL.createObjectURL(new Blob([data], { type: source.mimeType }));
+                        texture.dataReadyObservable.notifyObservers(texture);
+                    };
+                    if (!source.uri) {
+                        var bufferView = this._gltf.bufferViews[source.bufferView];
+                        this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, 1, GLTF2.EComponentType.UNSIGNED_BYTE, setTextureData);
+                    }
+                    else if (GLTF2.GLTFUtils.IsBase64(source.uri)) {
+                        setTextureData(new Uint8Array(GLTF2.GLTFUtils.DecodeBase64(source.uri)));
+                    }
+                    else {
+                        BABYLON.Tools.LoadFile(this._rootUrl + source.uri, setTextureData, function (event) {
+                            if (!_this._disposed) {
+                                _this._onProgress(event);
+                            }
+                        }, this._babylonScene.database, true, function (request) {
+                            _this._onError("Failed to load file '" + source.uri + "': " + request.status + " " + request.statusText);
+                        });
+                    }
                 }
                 babylonTexture.coordinatesIndex = texCoord;
                 babylonTexture.wrapU = GLTF2.GLTFUtils.GetTextureWrapMode(sampler.wrapS);
                 babylonTexture.wrapV = GLTF2.GLTFUtils.GetTextureWrapMode(sampler.wrapT);
                 babylonTexture.name = texture.name || "texture" + textureInfo.index;
-                // Cache the texture
-                texture.babylonTextures = texture.babylonTextures || [];
-                texture.babylonTextures[texCoord] = babylonTexture;
                 if (this._parent.onTextureLoaded) {
                     this._parent.onTextureLoaded(babylonTexture);
                 }
@@ -1419,7 +1424,7 @@ var BABYLON;
             GLTFUtils.GetTextureSamplingMode = function (magFilter, minFilter) {
                 // Set defaults if undefined
                 magFilter = magFilter === undefined ? GLTF2.ETextureMagFilter.LINEAR : magFilter;
-                minFilter = minFilter === undefined ? GLTF2.ETextureMinFilter.LINEAR_MIPMAP_NEAREST : minFilter;
+                minFilter = minFilter === undefined ? GLTF2.ETextureMinFilter.LINEAR_MIPMAP_LINEAR : minFilter;
                 if (magFilter === GLTF2.ETextureMagFilter.LINEAR) {
                     switch (minFilter) {
                         case GLTF2.ETextureMinFilter.NEAREST: return BABYLON.Texture.LINEAR_NEAREST;
@@ -1649,7 +1654,6 @@ var BABYLON;
                     babylonMaterial.microSurface = properties.glossinessFactor === undefined ? 1 : properties.glossinessFactor;
                     if (properties.diffuseTexture) {
                         babylonMaterial.albedoTexture = loader.loadTexture(properties.diffuseTexture);
-                        loader.loadMaterialAlphaProperties(material);
                     }
                     if (properties.specularGlossinessTexture) {
                         babylonMaterial.reflectivityTexture = loader.loadTexture(properties.specularGlossinessTexture);

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


+ 3 - 3
dist/preview release/loaders/babylon.glTFFileLoader.d.ts

@@ -754,7 +754,8 @@ declare module BABYLON.GLTF2 {
     interface IGLTFTexture extends IGLTFChildRootProperty {
         sampler?: number;
         source: number;
-        babylonTextures?: Texture[];
+        url?: string;
+        dataReadyObservable?: Observable<IGLTFTexture>;
     }
     interface IGLTFTextureInfo {
         index: number;
@@ -769,7 +770,6 @@ declare module BABYLON.GLTF2 {
         cameras?: IGLTFCamera[];
         extensionsUsed?: string[];
         extensionsRequired?: string[];
-        glExtensionsUsed?: string[];
         images?: IGLTFImage[];
         materials?: IGLTFMaterial[];
         meshes?: IGLTFMesh[];
@@ -795,9 +795,9 @@ declare module BABYLON.GLTF2 {
         private _errorCallback;
         private _renderReady;
         private _disposed;
-        private _objectURLs;
         private _blockPendingTracking;
         private _nonBlockingData;
+        private _rootMesh;
         private _renderReadyObservable;
         private _renderPendingCount;
         private _loaderPendingCount;

+ 53 - 49
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -2476,7 +2476,6 @@ var BABYLON;
             function GLTFLoader(parent) {
                 this._renderReady = false;
                 this._disposed = false;
-                this._objectURLs = new Array();
                 this._blockPendingTracking = false;
                 // Observable with boolean indicating success or error.
                 this._renderReadyObservable = new BABYLON.Observable();
@@ -2523,8 +2522,13 @@ var BABYLON;
                 }
                 this._disposed = true;
                 // Revoke object urls created during load
-                this._objectURLs.forEach(function (url) { return URL.revokeObjectURL(url); });
-                this._objectURLs.length = 0;
+                if (this._gltf.textures) {
+                    this._gltf.textures.forEach(function (texture) {
+                        if (texture.url) {
+                            URL.revokeObjectURL(texture.url);
+                        }
+                    });
+                }
                 this._gltf = undefined;
                 this._babylonScene = undefined;
                 this._rootUrl = undefined;
@@ -2600,7 +2604,7 @@ var BABYLON;
                 this._gltf = data.json;
                 var binaryBuffer;
                 var buffers = this._gltf.buffers;
-                if (buffers.length > 0 && buffers[0].uri === undefined) {
+                if (buffers && buffers[0].uri === undefined) {
                     binaryBuffer = buffers[0];
                 }
                 if (data.bin) {
@@ -2616,19 +2620,25 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._addRightHandToLeftHandRootTransform = function () {
-                var rootMesh = new BABYLON.Mesh("root", this._babylonScene);
-                rootMesh.scaling = new BABYLON.Vector3(1, 1, -1);
-                rootMesh.rotation.y = Math.PI;
+                this._rootMesh = new BABYLON.Mesh("root", this._babylonScene);
+                this._rootMesh.isVisible = false;
+                this._rootMesh.scaling = new BABYLON.Vector3(1, 1, -1);
+                this._rootMesh.rotation.y = Math.PI;
                 var nodes = this._gltf.nodes;
-                for (var i = 0; i < nodes.length; i++) {
-                    var mesh = nodes[i].babylonMesh;
-                    if (mesh && !mesh.parent) {
-                        mesh.parent = rootMesh;
+                if (nodes) {
+                    for (var i = 0; i < nodes.length; i++) {
+                        var mesh = nodes[i].babylonMesh;
+                        if (mesh && !mesh.parent) {
+                            mesh.parent = this._rootMesh;
+                        }
                     }
                 }
             };
             GLTFLoader.prototype._getMeshes = function () {
                 var meshes = [];
+                if (this._rootMesh) {
+                    meshes.push(this._rootMesh);
+                }
                 var nodes = this._gltf.nodes;
                 if (nodes) {
                     nodes.forEach(function (node) {
@@ -3444,6 +3454,7 @@ var BABYLON;
                         BABYLON.Tools.Warn("Invalid alpha mode '" + material.alphaMode + "'");
                         break;
                 }
+                babylonMaterial.alphaCutOff = material.alphaCutoff === undefined ? 0.5 : material.alphaCutoff;
             };
             GLTFLoader.prototype.loadTexture = function (textureInfo) {
                 var _this = this;
@@ -3452,28 +3463,12 @@ var BABYLON;
                 if (!texture || texture.source === undefined) {
                     return null;
                 }
-                // check the cache first
-                var babylonTexture;
-                if (texture.babylonTextures) {
-                    babylonTexture = texture.babylonTextures[texCoord];
-                    if (!babylonTexture) {
-                        for (var i = 0; i < texture.babylonTextures.length; i++) {
-                            babylonTexture = texture.babylonTextures[i];
-                            if (babylonTexture) {
-                                babylonTexture = babylonTexture.clone();
-                                babylonTexture.coordinatesIndex = texCoord;
-                                break;
-                            }
-                        }
-                    }
-                    return babylonTexture;
-                }
                 var source = this._gltf.images[texture.source];
                 var sampler = (texture.sampler === undefined ? {} : this._gltf.samplers[texture.sampler]);
                 var noMipMaps = (sampler.minFilter === GLTF2.ETextureMinFilter.NEAREST || sampler.minFilter === GLTF2.ETextureMinFilter.LINEAR);
                 var samplingMode = GLTF2.GLTFUtils.GetTextureSamplingMode(sampler.magFilter, sampler.minFilter);
                 this.addPendingData(texture);
-                babylonTexture = new BABYLON.Texture(null, this._babylonScene, noMipMaps, false, samplingMode, function () {
+                var babylonTexture = new BABYLON.Texture(null, this._babylonScene, noMipMaps, false, samplingMode, function () {
                     if (!_this._disposed) {
                         _this.removePendingData(texture);
                     }
@@ -3483,34 +3478,44 @@ var BABYLON;
                         _this.removePendingData(texture);
                     }
                 });
-                var setTextureData = function (data) {
-                    var url = URL.createObjectURL(new Blob([data], { type: source.mimeType }));
-                    _this._objectURLs.push(url);
-                    babylonTexture.updateURL(url);
-                };
-                if (!source.uri) {
-                    var bufferView = this._gltf.bufferViews[source.bufferView];
-                    this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, 1, GLTF2.EComponentType.UNSIGNED_BYTE, setTextureData);
+                if (texture.url) {
+                    babylonTexture.updateURL(texture.url);
                 }
-                else if (GLTF2.GLTFUtils.IsBase64(source.uri)) {
-                    setTextureData(new Uint8Array(GLTF2.GLTFUtils.DecodeBase64(source.uri)));
+                else if (texture.dataReadyObservable) {
+                    texture.dataReadyObservable.add(function (texture) {
+                        babylonTexture.updateURL(texture.url);
+                    });
                 }
                 else {
-                    BABYLON.Tools.LoadFile(this._rootUrl + source.uri, setTextureData, function (event) {
-                        if (!_this._disposed) {
-                            _this._onProgress(event);
-                        }
-                    }, this._babylonScene.database, true, function (request) {
-                        _this._onError("Failed to load file '" + source.uri + "': " + request.status + " " + request.statusText);
+                    texture.dataReadyObservable = new BABYLON.Observable();
+                    texture.dataReadyObservable.add(function (texture) {
+                        babylonTexture.updateURL(texture.url);
                     });
+                    var setTextureData = function (data) {
+                        texture.url = URL.createObjectURL(new Blob([data], { type: source.mimeType }));
+                        texture.dataReadyObservable.notifyObservers(texture);
+                    };
+                    if (!source.uri) {
+                        var bufferView = this._gltf.bufferViews[source.bufferView];
+                        this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, 1, GLTF2.EComponentType.UNSIGNED_BYTE, setTextureData);
+                    }
+                    else if (GLTF2.GLTFUtils.IsBase64(source.uri)) {
+                        setTextureData(new Uint8Array(GLTF2.GLTFUtils.DecodeBase64(source.uri)));
+                    }
+                    else {
+                        BABYLON.Tools.LoadFile(this._rootUrl + source.uri, setTextureData, function (event) {
+                            if (!_this._disposed) {
+                                _this._onProgress(event);
+                            }
+                        }, this._babylonScene.database, true, function (request) {
+                            _this._onError("Failed to load file '" + source.uri + "': " + request.status + " " + request.statusText);
+                        });
+                    }
                 }
                 babylonTexture.coordinatesIndex = texCoord;
                 babylonTexture.wrapU = GLTF2.GLTFUtils.GetTextureWrapMode(sampler.wrapS);
                 babylonTexture.wrapV = GLTF2.GLTFUtils.GetTextureWrapMode(sampler.wrapT);
                 babylonTexture.name = texture.name || "texture" + textureInfo.index;
-                // Cache the texture
-                texture.babylonTextures = texture.babylonTextures || [];
-                texture.babylonTextures[texCoord] = babylonTexture;
                 if (this._parent.onTextureLoaded) {
                     this._parent.onTextureLoaded(babylonTexture);
                 }
@@ -3577,7 +3582,7 @@ var BABYLON;
             GLTFUtils.GetTextureSamplingMode = function (magFilter, minFilter) {
                 // Set defaults if undefined
                 magFilter = magFilter === undefined ? GLTF2.ETextureMagFilter.LINEAR : magFilter;
-                minFilter = minFilter === undefined ? GLTF2.ETextureMinFilter.LINEAR_MIPMAP_NEAREST : minFilter;
+                minFilter = minFilter === undefined ? GLTF2.ETextureMinFilter.LINEAR_MIPMAP_LINEAR : minFilter;
                 if (magFilter === GLTF2.ETextureMagFilter.LINEAR) {
                     switch (minFilter) {
                         case GLTF2.ETextureMinFilter.NEAREST: return BABYLON.Texture.LINEAR_NEAREST;
@@ -3807,7 +3812,6 @@ var BABYLON;
                     babylonMaterial.microSurface = properties.glossinessFactor === undefined ? 1 : properties.glossinessFactor;
                     if (properties.diffuseTexture) {
                         babylonMaterial.albedoTexture = loader.loadTexture(properties.diffuseTexture);
-                        loader.loadMaterialAlphaProperties(material);
                     }
                     if (properties.specularGlossinessTexture) {
                         babylonMaterial.reflectivityTexture = loader.loadTexture(properties.specularGlossinessTexture);

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 3 - 3
dist/preview release/loaders/babylon.glTFFileLoader.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/loaders/babylon.objFileLoader.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2 - 1
dist/preview release/materialsLibrary/babylon.cellMaterial.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/materialsLibrary/babylon.cellMaterial.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/materialsLibrary/babylon.customMaterial.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2 - 1
dist/preview release/materialsLibrary/babylon.fireMaterial.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/materialsLibrary/babylon.fireMaterial.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2 - 1
dist/preview release/materialsLibrary/babylon.furMaterial.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/materialsLibrary/babylon.furMaterial.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2 - 1
dist/preview release/materialsLibrary/babylon.gradientMaterial.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/materialsLibrary/babylon.gradientMaterial.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2 - 1
dist/preview release/materialsLibrary/babylon.lavaMaterial.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/materialsLibrary/babylon.lavaMaterial.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 2 - 1
dist/preview release/materialsLibrary/babylon.normalMaterial.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/materialsLibrary/babylon.normalMaterial.min.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 1
dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.min.js


+ 0 - 0
dist/preview release/materialsLibrary/babylon.simpleMaterial.js


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott