فهرست منبع

Export 3ds Max > Node hierarchy + MultiMaterials

noalak 8 سال پیش
والد
کامیت
8b89f4a230
25فایلهای تغییر یافته به همراه717 افزوده شده و 491 حذف شده
  1. 11 2
      Exporters/3ds Max/BabylonExport.Entities/BabylonCamera.cs
  2. 3 2
      Exporters/3ds Max/BabylonExport.Entities/BabylonExport.Entities.csproj
  3. 16 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonScene.cs
  4. 12 29
      Exporters/3ds Max/BabylonExport.Entities/BabylonVector3.cs
  5. 1 1
      Exporters/3ds Max/BabylonExport.Entities/packages.config
  6. 10 1
      Exporters/3ds Max/GltfExport.Entities/GLTF.cs
  7. 25 0
      Exporters/3ds Max/GltfExport.Entities/GLTFCamera.cs
  8. 20 0
      Exporters/3ds Max/GltfExport.Entities/GLTFCameraOrthographic.cs
  9. 20 0
      Exporters/3ds Max/GltfExport.Entities/GLTFCameraPerspective.cs
  10. 3 0
      Exporters/3ds Max/GltfExport.Entities/GLTFExport.Entities.csproj
  11. 112 0
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Camera.cs
  12. 47 0
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Light.cs
  13. 23 24
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Material.cs
  14. 260 186
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Mesh.cs
  15. 4 4
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Texture.cs
  16. 60 26
      Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.cs
  17. 42 0
      Exporters/3ds Max/Max2Babylon/2017/JsonTextWriterBounded.cs
  18. 3 0
      Exporters/3ds Max/Max2Babylon/2017/Max2Babylon2017.csproj
  19. 16 6
      Exporters/3ds Max/Max2Babylon/2017/Properties/Resources.Designer.cs
  20. 4 0
      Exporters/3ds Max/Max2Babylon/2017/Properties/Resources.resx
  21. BIN
      Exporters/3ds Max/Max2Babylon/2017/Resources/Logo_Exporter_v3.jpg
  22. 19 6
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Camera.cs
  23. 4 4
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Mesh.cs
  24. 2 3
      Exporters/3ds Max/Max2Babylon/Forms/ExporterForm.Designer.cs
  25. 0 197
      Exporters/3ds Max/Max2Babylon/Forms/ExporterForm.resx

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

@@ -5,6 +5,12 @@ namespace BabylonExport.Entities
     [DataContract]
     public class BabylonCamera : BabylonNode
     {
+        public enum CameraMode
+        {
+            PERSPECTIVE_CAMERA = 0,
+            ORTHOGRAPHIC_CAMERA = 1
+        }
+
         [DataMember]
         public string lockedTargetId { get; set; }
 
@@ -18,6 +24,9 @@ namespace BabylonExport.Entities
         public float[] rotation { get; set; }
 
         [DataMember]
+        public float[] rotationQuaternion { get; set; }
+
+        [DataMember]
         public float[] target { get; set; }
 
         [DataMember]
@@ -48,7 +57,7 @@ namespace BabylonExport.Entities
         public float[] ellipsoid { get; set; }
 
         [DataMember]
-        public int mode { get; set; }
+        public CameraMode mode { get; set; }
 
         [DataMember]
         public float? orthoLeft { get; set; }
@@ -84,7 +93,7 @@ namespace BabylonExport.Entities
             inertia = 0.9f;
             interaxialDistance = 0.0637f;
 
-            mode = 0;
+            mode = CameraMode.PERSPECTIVE_CAMERA;
             orthoLeft = null;
             orthoRight = null;
             orthoBottom = null;

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

@@ -9,7 +9,7 @@
     <AppDesignerFolder>Properties</AppDesignerFolder>
     <RootNamespace>BabylonExport.Entities</RootNamespace>
     <AssemblyName>BabylonExport.Entities</AssemblyName>
-    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
     <SccProjectName>SAK</SccProjectName>
     <SccLocalPath>SAK</SccLocalPath>
@@ -91,13 +91,14 @@
     <None Include="packages.config" />
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!--
   <Import Project=".\packages\SharpDX.2.6.3\build\SharpDX.targets" Condition="Exists('.\packages\SharpDX.2.6.3\build\SharpDX.targets')" />
   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
     <PropertyGroup>
       <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
     </PropertyGroup>
     <Error Condition="!Exists('.\packages\SharpDX.2.6.3\build\SharpDX.targets')" Text="$([System.String]::Format('$(ErrorText)', '.\packages\SharpDX.2.6.3\build\SharpDX.targets'))" />
-  </Target>
+  </Target> -->
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
   <Target Name="BeforeBuild">

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

@@ -142,6 +142,22 @@ namespace BabylonExport.Entities
             {
                 var camera = new BabylonCamera { name = "Default camera", id = Guid.NewGuid().ToString() };
 
+                // Default camera init gives infinit values
+                // Indeed, float.MaxValue - float.MinValue always leads to infinity
+                // Is MaxVector and MinVector update missing?
+                MaxVector = new BabylonVector3
+                {
+                    X = 1,
+                    Y = 1,
+                    Z = 1
+                };
+                MinVector = new BabylonVector3
+                {
+                    X = -1,
+                    Y = -1,
+                    Z = -1
+                };
+
                 var distanceVector = MaxVector - MinVector;
                 var midPoint = MinVector + distanceVector / 2;
                 camera.target = midPoint.ToArray();

+ 12 - 29
Exporters/3ds Max/BabylonExport.Entities/BabylonVector3.cs

@@ -38,40 +38,23 @@ namespace BabylonExport.Entities
             return new BabylonVector3 { X = a.X * b, Y = a.Y * b, Z = a.Z * b };
         }
 
-        /**
-         * Returns a new Quaternion object, computed from the Vector3 coordinates.
-         */
-        public BabylonQuaternion toQuaternion()
+        public BabylonQuaternion toQuaternionGltf()
         {
-            var result = new BabylonQuaternion();
-
-            var cosxPlusz = Math.Cos((this.X + this.Z) * 0.5);
-            var sinxPlusz = Math.Sin((this.X + this.Z) * 0.5);
-            var coszMinusx = Math.Cos((this.Z - this.X) * 0.5);
-            var sinzMinusx = Math.Sin((this.Z - this.X) * 0.5);
-            var cosy = Math.Cos(this.Y * 0.5);
-            var siny = Math.Sin(this.Y * 0.5);
-
-            result.X = (float)(coszMinusx * siny);
-            result.Y = (float)(-sinzMinusx * siny);
-            result.Z = (float)(sinxPlusz * cosy);
-            result.W = (float)(cosxPlusz * cosy);
-            return result;
+            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;
         }
 
-        ///**
-        // * Returns a new Quaternion object, computed from the Vector3 coordinates.
-        // */
-        //public BabylonQuaternion toQuaternion()
-        //{
-        //    return RotationYawPitchRollToRef(Y,X,Z);
-        //}
-
-
         /**
+         * (Copy pasted from babylon)
          * Sets the passed quaternion "result" from the passed float Euler angles (y, x, z).  
          */
-        public BabylonQuaternion RotationYawPitchRollToRef(float yaw, float pitch, float roll)
+        private BabylonQuaternion RotationYawPitchRollToRefBabylon(float yaw, float pitch, float roll)
         {
             // Produces a quaternion from Euler angles in the z-y-x orientation (Tait-Bryan angles)
             var halfRoll = roll * 0.5;
@@ -92,5 +75,5 @@ namespace BabylonExport.Entities
             result.W = (float)((cosYaw * cosPitch * cosRoll) + (sinYaw * sinPitch * sinRoll));
             return result;
         }
-}
+    }
 }

+ 1 - 1
Exporters/3ds Max/BabylonExport.Entities/packages.config

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="SharpDX" version="2.6.3" targetFramework="net35" />
+  <package id="SharpDX" version="2.6.3" targetFramework="net35" requireReinstallation="true" />
 </packages>

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

@@ -10,7 +10,7 @@ namespace GLTFExport.Entities
         public GLTFAsset asset { get; set; }
 
         [DataMember(EmitDefaultValue = false)]
-        public int scene { get; set; }
+        public int? scene { get; set; }
 
         [DataMember(EmitDefaultValue = false)]
         public GLTFScene[] scenes { get; set; }
@@ -19,6 +19,9 @@ namespace GLTFExport.Entities
         public GLTFNode[] nodes { get; set; }
 
         [DataMember(EmitDefaultValue = false)]
+        public GLTFCamera[] cameras { get; set; }
+
+        [DataMember(EmitDefaultValue = false)]
         public GLTFMesh[] meshes { get; set; }
 
         [DataMember(EmitDefaultValue = false)]
@@ -45,6 +48,7 @@ namespace GLTFExport.Entities
         public string OutputPath { get; private set; }
 
         public List<GLTFNode> NodesList { get; private set; }
+        public List<GLTFCamera> CamerasList { get; private set; }
         public List<GLTFBuffer> BuffersList { get; private set; }
         public List<GLTFBufferView> BufferViewsList { get; private set; }
         public List<GLTFAccessor> AccessorsList { get; private set; }
@@ -59,6 +63,7 @@ namespace GLTFExport.Entities
             OutputPath = outputPath;
 
             NodesList = new List<GLTFNode>();
+            CamerasList = new List<GLTFCamera>();
             BuffersList = new List<GLTFBuffer>();
             BufferViewsList = new List<GLTFBufferView>();
             AccessorsList = new List<GLTFAccessor>();
@@ -79,6 +84,10 @@ namespace GLTFExport.Entities
                 nodes = NodesList.ToArray();
                 NodesList.ForEach(node => node.Prepare());
             }
+            if (CamerasList.Count > 0)
+            {
+                cameras = CamerasList.ToArray();
+            }
             if (BuffersList.Count > 0)
             {
                 buffers = BuffersList.ToArray();

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

@@ -0,0 +1,25 @@
+using System.Runtime.Serialization;
+
+namespace GLTFExport.Entities
+{
+    [DataContract]
+    public class GLTFCamera : GLTFIndexedChildRootProperty
+    {
+        public enum CameraType
+        {
+            perspective,
+            orthographic
+        }
+
+        [DataMember(EmitDefaultValue = false)]
+        public GLTFCameraOrthographic orthographic { get; set; }
+
+        [DataMember(EmitDefaultValue = false)]
+        public GLTFCameraPerspective perspective { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public string type { get; set; }
+
+        public GLTFNode gltfNode;
+    }
+}

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

@@ -0,0 +1,20 @@
+using System.Runtime.Serialization;
+
+namespace GLTFExport.Entities
+{
+    [DataContract]
+    public class GLTFCameraOrthographic : GLTFProperty
+    {
+        [DataMember(IsRequired = true)]
+        public float xmag { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public float ymag { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public float zfar { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public float znear { get; set; }
+    }
+}

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

@@ -0,0 +1,20 @@
+using System.Runtime.Serialization;
+
+namespace GLTFExport.Entities
+{
+    [DataContract]
+    public class GLTFCameraPerspective : GLTFProperty
+    {
+        [DataMember(EmitDefaultValue = false)]
+        public float? aspectRatio { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public float yfov { get; set; }
+
+        [DataMember(EmitDefaultValue = false)]
+        public float? zfar { get; set; }
+
+        [DataMember(IsRequired = true)]
+        public float znear { get; set; }
+    }
+}

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

@@ -44,6 +44,9 @@
     <Compile Include="GLTFAccessor.cs" />
     <Compile Include="GLTFBufferView.cs" />
     <Compile Include="GLTFBuffer.cs" />
+    <Compile Include="GLTFCameraPerspective.cs" />
+    <Compile Include="GLTFCameraOrthographic.cs" />
+    <Compile Include="GLTFCamera.cs" />
     <Compile Include="GLTFSampler.cs" />
     <Compile Include="GLTFIndexedChildRootProperty.cs" />
     <Compile Include="GLTFImage.cs" />

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

@@ -0,0 +1,112 @@
+using BabylonExport.Entities;
+using GLTFExport.Entities;
+
+namespace Max2Babylon
+{
+    partial class BabylonExporter
+    {
+        // TODO - Test if ok with a gltf viewer working with custom camera (babylon loader/sandbox doesn't load them)
+        private GLTFCamera ExportCamera(BabylonCamera babylonCamera, GLTF gltf, GLTFNode gltfParentNode)
+        {
+            RaiseMessage("GLTFExporter.Camera | Export camera named: " + babylonCamera.name, 1);
+
+            // --------------------------
+            // ---------- Node ----------
+            // --------------------------
+
+            RaiseMessage("GLTFExporter.Camera | Node", 2);
+            // Node
+            var gltfNode = new GLTFNode();
+            gltfNode.name = babylonCamera.name;
+            gltfNode.index = gltf.NodesList.Count;
+            gltf.NodesList.Add(gltfNode);
+
+            // Hierarchy
+            if (gltfParentNode != null)
+            {
+                RaiseMessage("GLTFExporter.Camera | Add " + babylonCamera.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.Camera | Add " + babylonCamera.name + " as root node to scene", 3);
+                gltf.scenes[0].NodesList.Add(gltfNode.index);
+            }
+
+            // Transform
+            gltfNode.translation = babylonCamera.position;
+            if (babylonCamera.rotationQuaternion != null)
+            {
+                gltfNode.rotation = babylonCamera.rotationQuaternion;
+            }
+            else
+            {
+                // Convert rotation vector to quaternion
+                BabylonVector3 rotationVector3 = new BabylonVector3
+                {
+                    X = babylonCamera.rotation[0],
+                    Y = babylonCamera.rotation[1],
+                    Z = babylonCamera.rotation[2]
+                };
+                gltfNode.rotation = rotationVector3.toQuaternionGltf().ToArray();
+            }
+            // No scaling defined for babylon camera. Use identity instead.
+            gltfNode.scale = new float[3] { 1, 1, 1 };
+
+
+            // --- prints ---
+
+            RaiseMessage("GLTFExporter.Camera | babylonCamera data", 2);
+            RaiseMessage("GLTFExporter.Camera | babylonCamera.type=" + babylonCamera.type, 3);
+            RaiseMessage("GLTFExporter.Camera | babylonCamera.fov=" + babylonCamera.fov, 3);
+            RaiseMessage("GLTFExporter.Camera | babylonCamera.maxZ=" + babylonCamera.maxZ, 3);
+            RaiseMessage("GLTFExporter.Camera | babylonCamera.minZ=" + babylonCamera.minZ, 3);
+
+
+            // --------------------------
+            // ------- gltfCamera -------
+            // --------------------------
+
+            RaiseMessage("GLTFExporter.Camera | create gltfCamera", 2);
+
+            // Camera
+            var gltfCamera = new GLTFCamera { name = babylonCamera.name };
+            gltfCamera.index = gltf.CamerasList.Count;
+            gltf.CamerasList.Add(gltfCamera);
+            gltfNode.camera = gltfCamera.index;
+            gltfCamera.gltfNode = gltfNode;
+
+            // Camera type
+            switch (babylonCamera.mode)
+            {
+                case (BabylonCamera.CameraMode.ORTHOGRAPHIC_CAMERA):
+                    var gltfCameraOrthographic = new GLTFCameraOrthographic();
+                    gltfCameraOrthographic.xmag = 1; // TODO - How to retreive value from babylon? xmag:The floating-point horizontal magnification of the view
+                    gltfCameraOrthographic.ymag = 1; // TODO - How to retreive value from babylon? ymag:The floating-point vertical magnification of the view
+                    gltfCameraOrthographic.zfar = babylonCamera.maxZ;
+                    gltfCameraOrthographic.znear = babylonCamera.minZ;
+
+                    gltfCamera.type = GLTFCamera.CameraType.orthographic.ToString();
+                    gltfCamera.orthographic = gltfCameraOrthographic;
+                    break;
+                case (BabylonCamera.CameraMode.PERSPECTIVE_CAMERA):
+                    var gltfCameraPerspective = new GLTFCameraPerspective();
+                    gltfCameraPerspective.aspectRatio = null; // 0.8f; // TODO - How to retreive value from babylon? The aspect ratio in babylon is computed based on the engine rather than set on a camera (aspectRatio = _gl.drawingBufferWidth / _gl.drawingBufferHeight)
+                    gltfCameraPerspective.yfov = babylonCamera.fov; // WARNING - Babylon camera fov mode is assumed to be vertical (FOVMODE_VERTICAL_FIXED)
+                    gltfCameraPerspective.zfar = babylonCamera.maxZ;
+                    gltfCameraPerspective.znear = babylonCamera.minZ;
+
+                    gltfCamera.type = GLTFCamera.CameraType.perspective.ToString();
+                    gltfCamera.perspective = gltfCameraPerspective;
+                    break;
+                default:
+                    RaiseError("GLTFExporter.Camera | camera mode not found");
+                    break;
+            }
+            
+            return gltfCamera;
+        }
+    }
+}

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

@@ -0,0 +1,47 @@
+using BabylonExport.Entities;
+using GLTFExport.Entities;
+
+namespace Max2Babylon
+{
+    partial class BabylonExporter
+    {
+        private GLTFNode ExportLight(BabylonLight babylonLight, GLTF gltf, GLTFNode gltfParentNode)
+        {
+            RaiseMessage("GLTFExporter.Light | ExportLight babylonLight.name=" + babylonLight.name, 1);
+
+            // --------------------------
+            // ---------- Node ----------
+            // --------------------------
+
+            RaiseMessage("GLTFExporter.Light | Node", 2);
+            // Node
+            var gltfNode = new GLTFNode();
+            gltfNode.name = babylonLight.name;
+            gltfNode.index = gltf.NodesList.Count;
+            gltf.NodesList.Add(gltfNode);
+
+            // Hierarchy
+            if (gltfParentNode != null)
+            {
+                RaiseMessage("GLTFExporter.Light | Add " + babylonLight.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.Light | Add " + babylonLight.name + " as root node to scene", 3);
+                gltf.scenes[0].NodesList.Add(gltfNode.index);
+            }
+
+            // Transform
+            gltfNode.translation = babylonLight.position;
+            // No rotation defined for babylon light. Use identity instead.
+            gltfNode.rotation = new float[4] { 0, 0, 0, 1 };
+            // No scaling defined for babylon light. Use identity instead.
+            gltfNode.scale = new float[3] { 1, 1, 1 };
+
+            return gltfNode;
+        }
+    }
+}

+ 23 - 24
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Material.cs

@@ -5,73 +5,69 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        //readonly List<IIGameMaterial> referencedMaterials = new List<IIGameMaterial>();
-
         private void ExportMaterial(BabylonMaterial babylonMaterial, GLTF gltf)
         {
             var name = babylonMaterial.name;
             var id = babylonMaterial.id;
 
-            RaiseMessage("GLTFExporter.Material | ExportMaterial name=" + name, 1);
+            RaiseMessage("GLTFExporter.Material | Export material named: " + name, 1);
 
             if (babylonMaterial.GetType() == typeof(BabylonStandardMaterial))
             {
-                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial != null", 1);
-
                 var babylonStandardMaterial = babylonMaterial as BabylonStandardMaterial;
 
 
                 // --- prints ---
 
-                RaiseMessage("GLTFExporter.Material | babylonMaterial data", 1);
-                RaiseMessage("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 2);
-                RaiseMessage("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 2);
-                RaiseMessage("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 2);
+                RaiseMessage("GLTFExporter.Material | babylonMaterial data", 2);
+                RaiseMessage("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 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], 2);
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.ambient[" + i + "]=" + babylonStandardMaterial.ambient[i], 3);
                 }
 
                 // Diffuse
-                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuse.Length=" + babylonStandardMaterial.diffuse.Length, 2);
+                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], 2);
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuse[" + i + "]=" + babylonStandardMaterial.diffuse[i], 3);
                 }
                 if (babylonStandardMaterial.diffuseTexture == null)
                 {
-                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture=null", 2);
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture=null", 3);
                 }
 
                 // Normal / bump
                 if (babylonStandardMaterial.bumpTexture == null)
                 {
-                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.bumpTexture=null", 2);
+                    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], 2);
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specular[" + i + "]=" + babylonStandardMaterial.specular[i], 3);
                 }
-                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 2);
+                RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 3);
 
                 // Occlusion
                 if (babylonStandardMaterial.ambientTexture == null)
                 {
-                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.ambientTexture=null", 2);
+                    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], 2);
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.emissive[" + i + "]=" + babylonStandardMaterial.emissive[i], 3);
                 }
                 if (babylonStandardMaterial.emissiveTexture == null)
                 {
-                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture=null", 2);
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture=null", 3);
                 }
 
 
@@ -79,7 +75,7 @@ namespace Max2Babylon
                 // --------- gltfMaterial ---------
                 // --------------------------------
 
-                RaiseMessage("GLTFExporter.Material | create gltfMaterial", 1);
+                RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2);
                 var gltfMaterial = new GLTFMaterial
                 {
                     name = name
@@ -113,10 +109,11 @@ namespace Max2Babylon
                 // --- gltfPbrMetallicRoughness ---
                 // --------------------------------
 
-                RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 1);
+                RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2);
                 var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness();
                 gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness;
 
+                // TODO - Retreive diffuse or albedo?
                 // Base color
                 var babylonDiffuseColor = babylonStandardMaterial.diffuse;
                 gltfPbrMetallicRoughness.baseColorFactor = new float[4]
@@ -127,7 +124,7 @@ namespace Max2Babylon
                     babylonMaterial.alpha
                 };
                 gltfPbrMetallicRoughness.baseColorTexture = ExportTexture(babylonStandardMaterial.diffuseTexture, gltf);
-
+                 
                 // TODO - Metallic roughness
                 gltfPbrMetallicRoughness.metallicFactor = 0; // Non metal
                 // TODO - roughnessFactor
@@ -137,13 +134,15 @@ namespace Max2Babylon
 
         private void getAlphaMode(BabylonStandardMaterial babylonMaterial, out string alphaMode, out float? alphaCutoff)
         {
-            if (babylonMaterial.diffuseTexture.hasAlpha)
+            if (babylonMaterial.diffuseTexture != null && babylonMaterial.diffuseTexture.hasAlpha)
             {
+                // TODO - Babylon standard material is assumed to useAlphaFromDiffuseTexture. If not, the alpha mode is a mask.
                 alphaMode = GLTFMaterial.AlphaMode.BLEND.ToString();
             }
             else
             {
-                alphaMode = GLTFMaterial.AlphaMode.OPAQUE.ToString();
+                // glTF alpha mode default value is "OPAQUE"
+                alphaMode = null; // GLTFMaterial.AlphaMode.OPAQUE.ToString();
             }
             alphaCutoff = null;
         }

+ 260 - 186
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.Mesh.cs

@@ -10,15 +10,15 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, GLTFNode gltfParentNode)
+        private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, GLTFNode gltfParentNode, BabylonScene babylonScene)
         {
-            RaiseMessage("GLTFExporter.Mesh | ExportMesh babylonMesh.name=" + babylonMesh.name, 1);
+            RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1);
 
             // --------------------------
             // ---------- Node ----------
             // --------------------------
 
-            RaiseMessage("GLTFExporter.Mesh | Node", 1);
+            RaiseMessage("GLTFExporter.Mesh | Node", 2);
             // Node
             var gltfNode = new GLTFNode();
             gltfNode.name = babylonMesh.name;
@@ -28,14 +28,14 @@ namespace Max2Babylon
             // Hierarchy
             if (gltfParentNode != null)
             {
-                RaiseMessage("GLTFExporter.Mesh | Add " + babylonMesh.name + " as child to " + gltfParentNode.name, 2);
+                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", 2);
+                RaiseMessage("GLTFExporter.Mesh | Add " + babylonMesh.name + " as root node to scene", 3);
                 gltf.scenes[0].NodesList.Add(gltfNode.index);
             }
 
@@ -48,17 +48,13 @@ namespace Max2Babylon
             else
             {
                 // Convert rotation vector to quaternion
-                // TODO - Fix it
                 BabylonVector3 rotationVector3 = new BabylonVector3
                 {
                     X = babylonMesh.rotation[0],
                     Y = babylonMesh.rotation[1],
                     Z = babylonMesh.rotation[2]
                 };
-                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
-
-                RaiseMessage("GLTFExporter.Mesh | rotationVector3=[" + rotationVector3.X + "; " + rotationVector3.Y + "; " + rotationVector3.Z + "]", 2);
-                RaiseMessage("GLTFExporter.Mesh | gltfNode.rotation=[" + gltfNode.rotation[0] + "; " + gltfNode.rotation[1] + "; " + gltfNode.rotation[2] + "; " + gltfNode.rotation[3] + "]", 2);
+                gltfNode.rotation = rotationVector3.toQuaternionGltf().ToArray();
             }
             gltfNode.scale = babylonMesh.scaling;
 
@@ -67,56 +63,59 @@ namespace Max2Babylon
             // --- Mesh from babylon ----
             // --------------------------
 
-            RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 1);
+            RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2);
             // Retreive general data from babylon mesh
             int nbVertices = babylonMesh.positions.Length / 3;
+            bool isMultimaterial = babylonMesh.subMeshes.Length > 1;
             bool hasUV = babylonMesh.uvs != null && babylonMesh.uvs.Length > 0;
             bool hasUV2 = babylonMesh.uvs2 != null && babylonMesh.uvs2.Length > 0;
             bool hasColor = babylonMesh.colors != null && babylonMesh.colors.Length > 0;
 
-            RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 2);
-            RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 2);
-            RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 2);
-            RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 2);
+            RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 3);
+            RaiseMessage("GLTFExporter.Mesh | isMultimaterial=" + isMultimaterial, 3);
+            RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 3);
+            RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 3);
+            RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 3);
 
             // Retreive vertices data from babylon mesh
             List<GLTFGlobalVertex> globalVertices = new List<GLTFGlobalVertex>();
-            for (int i = 0; i < nbVertices; i++)
+            for (int indexVertex = 0; indexVertex < nbVertices; indexVertex++)
             {
                 GLTFGlobalVertex globalVertex = new GLTFGlobalVertex();
-                globalVertex.Position = createIPoint3(babylonMesh.positions, i);
-                globalVertex.Normal = createIPoint3(babylonMesh.normals, i);
+                globalVertex.Position = createIPoint3(babylonMesh.positions, indexVertex);
+                globalVertex.Normal = createIPoint3(babylonMesh.normals, indexVertex);
                 if (hasUV)
                 {
-                    globalVertex.UV = createIPoint2(babylonMesh.uvs, i);
+                    globalVertex.UV = createIPoint2(babylonMesh.uvs, indexVertex);
                     // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
                     // While for Babylon, it corresponds to the lower left corner of a texture image
                     globalVertex.UV.Y = 1 - globalVertex.UV.Y;
                 }
                 if (hasUV2)
                 {
-                    globalVertex.UV2 = createIPoint2(babylonMesh.uvs2, i);
+                    globalVertex.UV2 = createIPoint2(babylonMesh.uvs2, indexVertex);
                     // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
                     // While for Babylon, it corresponds to the lower left corner of a texture image
                     globalVertex.UV2.Y = 1 - globalVertex.UV2.Y;
                 }
                 if (hasColor)
                 {
-                    globalVertex.Color = createIPoint4(babylonMesh.colors, i).ToArray();
+                    globalVertex.Color = createIPoint4(babylonMesh.colors, indexVertex).ToArray();
                 }
 
                 globalVertices.Add(globalVertex);
             }
 
             // Retreive indices from babylon mesh
-            List<ushort> indices = new List<ushort>();
-            indices = babylonMesh.indices.ToList().ConvertAll(new Converter<int, ushort>(n => (ushort)n));
+            List<ushort> babylonIndices = new List<ushort>();
+            babylonIndices = babylonMesh.indices.ToList().ConvertAll(new Converter<int, ushort>(n => (ushort)n));
+            // For triangle primitives in gltf, the front face has a counter-clockwise (CCW) winding order
             // Swap face side
-            for (int i = 0; i < indices.Count; i += 3)
-            { 
-                var tmp = indices[i];
-                indices[i] = indices[i + 2];
-                indices[i+2] = tmp;
+            for (int i = 0; i < babylonIndices.Count; i += 3)
+            {
+                var tmp = babylonIndices[i];
+                babylonIndices[i] = babylonIndices[i + 2];
+                babylonIndices[i + 2] = tmp;
             }
 
 
@@ -124,7 +123,7 @@ namespace Max2Babylon
             // ------- Init glTF --------
             // --------------------------
 
-            RaiseMessage("GLTFExporter.Mesh | Init glTF", 1);
+            RaiseMessage("GLTFExporter.Mesh | Init glTF", 2);
             // Mesh
             var gltfMesh = new GLTFMesh { name = babylonMesh.name };
             gltfMesh.index = gltf.MeshesList.Count;
@@ -132,15 +131,6 @@ namespace Max2Babylon
             gltfNode.mesh = gltfMesh.index;
             gltfMesh.gltfNode = gltfNode;
 
-            // MeshPrimitive
-            var meshPrimitives = new List<GLTFMeshPrimitive>();
-            var meshPrimitive = new GLTFMeshPrimitive
-            {
-                attributes = new Dictionary<string, int>(),
-                mode = GLTFMeshPrimitive.FillMode.TRIANGLES // TODO reteive info from babylon material
-            };
-            meshPrimitives.Add(meshPrimitive);
-
             // Buffer
             var buffer = new GLTFBuffer
             {
@@ -171,51 +161,8 @@ namespace Max2Babylon
             bufferViewFloatVec3.index = gltf.BufferViewsList.Count;
             gltf.BufferViewsList.Add(bufferViewFloatVec3);
 
-            // Accessor - Indices
-            var accessorIndices = new GLTFAccessor
-            {
-                name = "accessorIndices",
-                bufferView = bufferViewScalar.index,
-                BufferView = bufferViewScalar,
-                componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT,
-                type = GLTFAccessor.TypeEnum.SCALAR.ToString()
-            };
-            accessorIndices.index = gltf.AccessorsList.Count;
-            gltf.AccessorsList.Add(accessorIndices);
-            meshPrimitive.indices = accessorIndices.index;
-
-            // Accessor - Positions
-            var accessorPositions = new GLTFAccessor
-            {
-                name = "accessorPositions",
-                bufferView = bufferViewFloatVec3.index,
-                BufferView = bufferViewFloatVec3,
-                componentType = GLTFAccessor.ComponentType.FLOAT,
-                type = GLTFAccessor.TypeEnum.VEC3.ToString(),
-                min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue },
-                max = new float[] { float.MinValue, float.MinValue, float.MinValue }
-            };
-            accessorPositions.index = gltf.AccessorsList.Count;
-            gltf.AccessorsList.Add(accessorPositions);
-            meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
-
-            // Accessor - Normals
-            var accessorNormals = new GLTFAccessor
-            {
-                name = "accessorNormals",
-                bufferView = bufferViewFloatVec3.index,
-                BufferView = bufferViewFloatVec3,
-                componentType = GLTFAccessor.ComponentType.FLOAT,
-                type = GLTFAccessor.TypeEnum.VEC3.ToString()
-            };
-            accessorNormals.index = gltf.AccessorsList.Count;
-            gltf.AccessorsList.Add(accessorNormals);
-            meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);
-
             // BufferView - Vector4
             GLTFBufferView bufferViewFloatVec4 = null;
-            // Accessor - Colors
-            GLTFAccessor accessorColors = null;
             if (hasColor)
             {
                 bufferViewFloatVec4 = new GLTFBufferView
@@ -228,23 +175,11 @@ namespace Max2Babylon
                 };
                 bufferViewFloatVec4.index = gltf.BufferViewsList.Count;
                 gltf.BufferViewsList.Add(bufferViewFloatVec4);
-
-                accessorColors = new GLTFAccessor
-                {
-                    name = "accessorColors",
-                    bufferView = bufferViewFloatVec4.index,
-                    BufferView = bufferViewFloatVec4,
-                    componentType = GLTFAccessor.ComponentType.FLOAT,
-                    type = GLTFAccessor.TypeEnum.VEC4.ToString()
-                };
-                accessorColors.index = gltf.AccessorsList.Count;
-                gltf.AccessorsList.Add(accessorColors);
-                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
             }
 
             // BufferView - Vector2
             GLTFBufferView bufferViewFloatVec2 = null;
-            if (hasUV ||hasUV2)
+            if (hasUV || hasUV2)
             {
                 bufferViewFloatVec2 = new GLTFBufferView
                 {
@@ -257,141 +192,280 @@ namespace Max2Babylon
                 gltf.BufferViewsList.Add(bufferViewFloatVec2);
             }
 
-            // Accessor - UV
-            GLTFAccessor accessorUVs = null;
-            if (hasUV)
+            // --------------------------
+            // ---- glTF primitives -----
+            // --------------------------
+
+            RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2);
+            var meshPrimitives = new List<GLTFMeshPrimitive>();
+
+            // Global vertices are sorted per submesh
+            var globalVerticesSubMeshes = new List<List<GLTFGlobalVertex>>();
+
+            // In gltf, indices of each mesh primitive are 0-based (ie: min value is 0)
+            // Thus, the gltf indices list is a concatenation of sub lists all 0-based
+            // Example for 2 triangles, each being a submesh:
+            //      babylonIndices = {0,1,2, 3,4,5} gives as result gltfIndicies = {0,1,2, 0,1,2}
+            var gltfIndices = new List<ushort>();
+            
+            foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes)
             {
-                accessorUVs = new GLTFAccessor
+                // --------------------------
+                // ------ SubMesh data ------
+                // --------------------------
+
+                List<GLTFGlobalVertex> globalVerticesSubMesh = globalVertices.GetRange(babylonSubMesh.verticesStart, babylonSubMesh.verticesCount);
+                globalVerticesSubMeshes.Add(globalVerticesSubMesh);
+
+                List<ushort> _indices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
+                // Indices of this submesh / primitive are updated to be 0-based
+                var minIndiceValue = _indices.Min(); // Should be equal to babylonSubMesh.indexStart
+                for (int indexIndice = 0; indexIndice < _indices.Count; indexIndice++)
+                {
+                    _indices[indexIndice] -= minIndiceValue;
+                }
+                gltfIndices.AddRange(_indices);
+
+                // --------------------------
+                // -- Init glTF primitive ---
+                // --------------------------
+
+                // MeshPrimitive
+                var meshPrimitive = new GLTFMeshPrimitive
+                {
+                    attributes = new Dictionary<string, int>()
+                };
+                meshPrimitives.Add(meshPrimitive);
+
+                // Accessor - Indices
+                var accessorIndices = new GLTFAccessor
+                {
+                    name = "accessorIndices",
+                    bufferView = bufferViewScalar.index,
+                    BufferView = bufferViewScalar,
+                    componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT,
+                    type = GLTFAccessor.TypeEnum.SCALAR.ToString()
+                };
+                accessorIndices.index = gltf.AccessorsList.Count;
+                gltf.AccessorsList.Add(accessorIndices);
+                meshPrimitive.indices = accessorIndices.index;
+
+                // Accessor - Positions
+                var accessorPositions = new GLTFAccessor
                 {
-                    name = "accessorUVs",
-                    bufferView = bufferViewFloatVec2.index,
-                    BufferView = bufferViewFloatVec2,
+                    name = "accessorPositions",
+                    bufferView = bufferViewFloatVec3.index,
+                    BufferView = bufferViewFloatVec3,
                     componentType = GLTFAccessor.ComponentType.FLOAT,
-                    type = GLTFAccessor.TypeEnum.VEC2.ToString()
+                    type = GLTFAccessor.TypeEnum.VEC3.ToString(),
+                    min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue },
+                    max = new float[] { float.MinValue, float.MinValue, float.MinValue }
                 };
-                accessorUVs.index = gltf.AccessorsList.Count;
-                gltf.AccessorsList.Add(accessorUVs);
-                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
-            }
+                accessorPositions.index = gltf.AccessorsList.Count;
+                gltf.AccessorsList.Add(accessorPositions);
+                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
 
-            // Accessor - UV2
-            GLTFAccessor accessorUV2s = null;
-            if (hasUV2)
-            {
-                accessorUV2s = new GLTFAccessor
+                // Accessor - Normals
+                var accessorNormals = new GLTFAccessor
                 {
-                    name = "accessorUV2s",
-                    bufferView = bufferViewFloatVec2.index,
-                    BufferView = bufferViewFloatVec2,
+                    name = "accessorNormals",
+                    bufferView = bufferViewFloatVec3.index,
+                    BufferView = bufferViewFloatVec3,
                     componentType = GLTFAccessor.ComponentType.FLOAT,
-                    type = GLTFAccessor.TypeEnum.VEC2.ToString()
+                    type = GLTFAccessor.TypeEnum.VEC3.ToString()
                 };
-                accessorUV2s.index = gltf.AccessorsList.Count;
-                gltf.AccessorsList.Add(accessorUV2s);
-                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
-            }
+                accessorNormals.index = gltf.AccessorsList.Count;
+                gltf.AccessorsList.Add(accessorNormals);
+                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);
 
+                // Accessor - Colors
+                GLTFAccessor accessorColors = null;
+                if (hasColor)
+                {
+                    accessorColors = new GLTFAccessor
+                    {
+                        name = "accessorColors",
+                        bufferView = bufferViewFloatVec4.index,
+                        BufferView = bufferViewFloatVec4,
+                        componentType = GLTFAccessor.ComponentType.FLOAT,
+                        type = GLTFAccessor.TypeEnum.VEC4.ToString()
+                    };
+                    accessorColors.index = gltf.AccessorsList.Count;
+                    gltf.AccessorsList.Add(accessorColors);
+                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
+                }
 
-            // --------------------------
-            // ------ Mesh as glTF ------
-            // --------------------------
+                // Accessor - UV
+                GLTFAccessor accessorUVs = null;
+                if (hasUV)
+                {
+                    accessorUVs = new GLTFAccessor
+                    {
+                        name = "accessorUVs",
+                        bufferView = bufferViewFloatVec2.index,
+                        BufferView = bufferViewFloatVec2,
+                        componentType = GLTFAccessor.ComponentType.FLOAT,
+                        type = GLTFAccessor.TypeEnum.VEC2.ToString()
+                    };
+                    accessorUVs.index = gltf.AccessorsList.Count;
+                    gltf.AccessorsList.Add(accessorUVs);
+                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
+                }
 
-            RaiseMessage("GLTFExporter.Mesh | Mesh as glTF", 1);
-            // Material
-            //TODO - Handle multimaterials
-            GLTFMaterial gltfMaterial = gltf.MaterialsList.Find(material => material.id == babylonMesh.materialId);
-            if (gltfMaterial != null)
-            {
-                meshPrimitive.material = gltfMaterial.index;
-            }
+                // Accessor - UV2
+                GLTFAccessor accessorUV2s = null;
+                if (hasUV2)
+                {
+                    accessorUV2s = new GLTFAccessor
+                    {
+                        name = "accessorUV2s",
+                        bufferView = bufferViewFloatVec2.index,
+                        BufferView = bufferViewFloatVec2,
+                        componentType = GLTFAccessor.ComponentType.FLOAT,
+                        type = GLTFAccessor.TypeEnum.VEC2.ToString()
+                    };
+                    accessorUV2s.index = gltf.AccessorsList.Count;
+                    gltf.AccessorsList.Add(accessorUV2s);
+                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
+                }
 
-            // Update min and max vertex position for each component (X, Y, Z)
-            globalVertices.ForEach((globalVertex) =>
-            {
-                var positionArray = new float[] { globalVertex.Position.X, globalVertex.Position.Y, globalVertex.Position.Z };
-                for (int indexComponent = 0; indexComponent < positionArray.Length; indexComponent++)
+                
+                // --------------------------
+                // - Update glTF primitive --
+                // --------------------------
+
+                RaiseMessage("GLTFExporter.Mesh | Mesh as glTF", 3);
+
+                // Material
+                if (babylonMesh.materialId != null)
                 {
-                    if (positionArray[indexComponent] < accessorPositions.min[indexComponent])
+                    // Retreive the babylon material
+                    var babylonMaterialId = babylonMesh.materialId;
+                    if (isMultimaterial)
                     {
-                        accessorPositions.min[indexComponent] = positionArray[indexComponent];
+                        var babylonMultiMaterials = new List<BabylonMultiMaterial>(babylonScene.multiMaterials);
+                        var babylonMultiMaterial = babylonMultiMaterials.Find(_babylonMultiMaterial => _babylonMultiMaterial.id == babylonMesh.materialId);
+                        babylonMaterialId = babylonMultiMaterial.materials[babylonSubMesh.materialIndex];
                     }
-                    if (positionArray[indexComponent] > accessorPositions.max[indexComponent])
+                    var babylonMaterials = new List<BabylonMaterial>(babylonScene.materials);
+                    var babylonMaterial = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId);
+
+                    // Update primitive material index
+                    var indexMaterial = babylonMaterialsToExport.FindIndex(_babylonMaterial => _babylonMaterial == babylonMaterial);
+                    if (indexMaterial == -1)
                     {
-                        accessorPositions.max[indexComponent] = positionArray[indexComponent];
+                        // Store material for exportation
+                        indexMaterial = babylonMaterialsToExport.Count;
+                        babylonMaterialsToExport.Add(babylonMaterial);
                     }
-                }
-            });
+                    meshPrimitive.material = indexMaterial;
 
-            // Update byte length and count of accessors, bufferViews and buffers
-            // Scalar
-            AddElementsToAccessor(accessorIndices, indices.Count);
-            // Vector3
-            bufferViewFloatVec3.byteOffset = buffer.byteLength;
-            AddElementsToAccessor(accessorPositions, globalVertices.Count);
-            AddElementsToAccessor(accessorNormals, globalVertices.Count);
-            // Vector4
-            if (hasColor)
-            {
-                bufferViewFloatVec4.byteOffset = buffer.byteLength;
-                AddElementsToAccessor(accessorColors, globalVertices.Count);
-            }
-            // Vector2
-            if (hasUV || hasUV2)
-            {
-                bufferViewFloatVec2.byteOffset = buffer.byteLength;
+                    // TODO - Add and retreive info from babylon material
+                    meshPrimitive.mode = GLTFMeshPrimitive.FillMode.TRIANGLES;
+                }
 
+                // Update min and max vertex position for each component (X, Y, Z)
+                globalVerticesSubMesh.ForEach((globalVertex) =>
+                {
+                    var positionArray = new float[] { globalVertex.Position.X, globalVertex.Position.Y, globalVertex.Position.Z };
+                    for (int indexComponent = 0; indexComponent < positionArray.Length; indexComponent++)
+                    {
+                        if (positionArray[indexComponent] < accessorPositions.min[indexComponent])
+                        {
+                            accessorPositions.min[indexComponent] = positionArray[indexComponent];
+                        }
+                        if (positionArray[indexComponent] > accessorPositions.max[indexComponent])
+                        {
+                            accessorPositions.max[indexComponent] = positionArray[indexComponent];
+                        }
+                    }
+                });
+
+                // Update byte length and count of accessors, bufferViews and buffers
+                // Scalar
+                AddElementsToAccessor(accessorIndices, _indices.Count);
+                // Vector3
+                AddElementsToAccessor(accessorPositions, globalVerticesSubMesh.Count);
+                AddElementsToAccessor(accessorNormals, globalVerticesSubMesh.Count);
+                // Vector4
+                if (hasColor)
+                {
+                    AddElementsToAccessor(accessorColors, globalVerticesSubMesh.Count);
+                }
+                // Vector2
                 if (hasUV)
                 {
-                    AddElementsToAccessor(accessorUVs, globalVertices.Count);
+                    AddElementsToAccessor(accessorUVs, globalVerticesSubMesh.Count);
                 }
                 if (hasUV2)
                 {
-                    AddElementsToAccessor(accessorUV2s, globalVertices.Count);
+                    AddElementsToAccessor(accessorUV2s, globalVerticesSubMesh.Count);
                 }
             }
+            gltfMesh.primitives = meshPrimitives.ToArray();
+            
+            // Update byte offset of bufferViews
+            GLTFBufferView lastBufferView = null;
+            gltf.BufferViewsList.FindAll(bufferView => bufferView.buffer == buffer.index).ForEach(bufferView =>
+            {
+                if (lastBufferView != null)
+                {
+                    bufferView.byteOffset = lastBufferView.byteOffset + lastBufferView.byteLength;
+                }
+                lastBufferView = bufferView;
+            });
 
 
             // --------------------------
             // --------- Saving ---------
             // --------------------------
 
-            string outputBinaryFile = Path.Combine(gltf.OutputPath,  gltfMesh.name + ".bin");
-            RaiseMessage("GLTFExporter.Mesh | Saving " + outputBinaryFile, 1);
+            string outputBinaryFile = Path.Combine(gltf.OutputPath, gltfMesh.name + ".bin");
+            RaiseMessage("GLTFExporter.Mesh | Saving " + outputBinaryFile, 2);
 
+            // Write data to binary file
             using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
             {
-                // Binary arrays
-                List<float> vertices = globalVertices.SelectMany(v => new[] { v.Position.X, v.Position.Y, v.Position.Z }).ToList();
-                List<float> normals = globalVertices.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToList();
+                // BufferView - Scalar
+                gltfIndices.ForEach(n => writer.Write(n));
 
-                List<float> colors = new List<float>();
-                if (hasColor)
+                // BufferView - Vector3
+                globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
                 {
-                    colors = globalVertices.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
-                }
+                    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> uvs = new List<float>();
-                if (hasUV)
-                {
-                    uvs = globalVertices.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList(); // No symetry required to perform 3dsMax => gltf conversion
-                }
+                    List<float> normals = globalVerticesSubMesh.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToList();
+                    normals.ForEach(n => writer.Write(n));
+                });
 
-                List<float> uvs2 = new List<float>();
-                if (hasUV2)
+                // BufferView - Vector4
+                globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
                 {
-                    uvs2 = globalVertices.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList(); // No symetry required to perform 3dsMax => gltf conversion
-                }
+                    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));
+                    }
+                });
 
-                // Write data to binary file
-                indices.ForEach(n => writer.Write(n));
-                vertices.ForEach(n => writer.Write(n));
-                normals.ForEach(n => writer.Write(n));
-                colors.ForEach(n => writer.Write(n));
-                uvs.ForEach(n => writer.Write(n));
+                // BufferView - Vector2
+                globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
+                {
+                    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));
+                    }
+                });
             }
 
-            gltfMesh.primitives = meshPrimitives.ToArray();
-
             return gltfMesh;
         }
 

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

@@ -12,13 +12,13 @@ namespace Max2Babylon
                 return null;
             }
 
-            RaiseMessage("GLTFExporter.Texture | ExportTexture babylonTexture.name=" + babylonTexture.name, 1);
+            RaiseMessage("GLTFExporter.Texture | Export texture named: " + babylonTexture.name, 1);
 
             // --------------------------
             // -------- Sampler ---------
             // --------------------------
 
-            RaiseMessage("GLTFExporter.Texture | create sampler", 1);
+            RaiseMessage("GLTFExporter.Texture | create sampler", 2);
             GLTFSampler gltfSampler = new GLTFSampler();
             gltfSampler.index = gltf.SamplersList.Count;
             gltf.SamplersList.Add(gltfSampler);
@@ -39,7 +39,7 @@ namespace Max2Babylon
             // --------- Image ----------
             // --------------------------
 
-            RaiseMessage("GLTFExporter.Texture | create image", 1);
+            RaiseMessage("GLTFExporter.Texture | create image", 2);
             GLTFImage gltfImage = new GLTFImage
             {
                 uri = babylonTexture.name
@@ -53,7 +53,7 @@ namespace Max2Babylon
             // -------- Texture ---------
             // --------------------------
 
-            RaiseMessage("GLTFExporter.Texture | create texture", 1);
+            RaiseMessage("GLTFExporter.Texture | create texture", 2);
             var gltfTexture = new GLTFTexture
             {
                 name = babylonTexture.name,

+ 60 - 26
Exporters/3ds Max/Max2Babylon/2017/Exporter/BabylonExporter.GLTFExporter.cs

@@ -11,6 +11,8 @@ namespace Max2Babylon
 {
     internal partial class BabylonExporter
     {
+        List<BabylonMaterial> babylonMaterialsToExport;
+
         public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary)
         {
             RaiseMessage("GLTFExporter | Export outputFile=" + outputFile + " generateBinary=" + generateBinary);
@@ -36,18 +38,6 @@ namespace Max2Babylon
             GLTFScene[] scenes = { scene };
             gltf.scenes = scenes;
 
-            // Materials
-            RaiseMessage("GLTFExporter | Exporting materials");
-            ReportProgressChanged(10);
-            var babylonMaterials = babylonScene.MaterialsList;
-            babylonMaterials.ForEach((babylonMaterial) =>
-            {
-                ExportMaterial(babylonMaterial, gltf);
-                CheckCancelled();
-            });
-            // TODO - Handle multimaterials
-            RaiseMessage(string.Format("GLTFExporter | Total: {0}", gltf.MaterialsList.Count /*+ glTF.MultiMaterialsList.Count*/), Color.Gray, 1);
-
             // Nodes
             List<BabylonNode> babylonNodes = new List<BabylonNode>();
             babylonNodes.AddRange(babylonScene.meshes);
@@ -55,11 +45,12 @@ namespace Max2Babylon
             babylonNodes.AddRange(babylonScene.cameras);
 
             // Root nodes
-            RaiseMessage("GLTFExporter | Exporting root nodes");
+            RaiseMessage("GLTFExporter | Exporting nodes");
             List<BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);
             var progressionStep = 80.0f / babylonRootNodes.Count;
-            var progression = 20.0f;
+            var progression = 10.0f;
             ReportProgressChanged((int)progression);
+            babylonMaterialsToExport = new List<BabylonMaterial>();
             babylonRootNodes.ForEach(babylonNode =>
             {
                 exportNodeRec(babylonNode, gltf, babylonScene);
@@ -68,15 +59,26 @@ namespace Max2Babylon
                 CheckCancelled();
             });
 
+            // 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);
+
             // Output
             RaiseMessage("GLTFExporter | Saving to output file");
             // Cast lists to arrays
             gltf.Prepare();
-            var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings()); // Standard serializer, not the optimized one
+            var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings());
             var sb = new StringBuilder();
             var sw = new StringWriter(sb, CultureInfo.InvariantCulture);
 
-            using (var jsonWriter = new JsonTextWriter(sw))
+            // 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);
@@ -99,22 +101,26 @@ namespace Max2Babylon
             GLTFNode gltfNode = null; 
             if (babylonNode.GetType() == typeof(BabylonMesh))
             {
-                GLTFMesh gltfMesh = ExportMesh(babylonNode as BabylonMesh, gltf, gltfParentNode);
-                if (gltfMesh != null)
-                {
-                    gltfNode = gltfMesh.gltfNode;
-                }
+                GLTFMesh gltfMesh = ExportMesh(babylonNode as BabylonMesh, gltf, gltfParentNode, babylonScene);
+                gltfNode = gltfMesh.gltfNode;
             }
             else if (babylonNode.GetType() == typeof(BabylonCamera))
             {
-                // TODO - Export camera nodes
-                RaiseError($"TODO - Export camera node named {babylonNode.name}", 1);
+                GLTFCamera gltfCamera = ExportCamera(babylonNode as BabylonCamera, gltf, gltfParentNode);
+                gltfNode = gltfCamera.gltfNode;
             }
             else if (babylonNode.GetType() == typeof(BabylonLight))
             {
-                // TODO - Export light nodes as empty nodes (no lights in glTF 2.0 core)
-                RaiseError($"TODO - Export light node named {babylonNode.name}", 1);
-                RaiseWarning($"GLTFExporter.Node | Light named {babylonNode.name} has children but lights are not exported with glTF 2.0 core version. An empty node is used instead.", 1);
+                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
             {
@@ -141,5 +147,33 @@ namespace Max2Babylon
 
             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(BabylonMesh) ||
+                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;
+        }
     }
 }

+ 42 - 0
Exporters/3ds Max/Max2Babylon/2017/JsonTextWriterBounded.cs

@@ -0,0 +1,42 @@
+using Newtonsoft.Json;
+using System.IO;
+
+namespace Max2Babylon
+{
+    class JsonTextWriterBounded : JsonTextWriter
+    {
+        public JsonTextWriterBounded(TextWriter textWriter)
+            : base(textWriter)
+        {
+        }
+
+        public override void WriteValue(float value)
+        {
+            if (float.IsNegativeInfinity(value))
+            {
+                value = float.MinValue;
+            }
+            else if (float.IsPositiveInfinity(value))
+            {
+                value = float.MaxValue;
+            }
+            base.WriteValue(value);
+        }
+
+        public override void WriteValue(float? value)
+        {
+            if (value.HasValue)
+            {
+                if (float.IsNegativeInfinity(value.Value))
+                {
+                    value = float.MinValue;
+                }
+                else if (float.IsPositiveInfinity(value.Value))
+                {
+                    value = float.MaxValue;
+                }
+            }
+            base.WriteValue(value);
+        }
+    }
+}

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

@@ -191,11 +191,14 @@
     <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>

+ 16 - 6
Exporters/3ds Max/Max2Babylon/2017/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/2017/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/2017/Resources/Logo_Exporter_v3.jpg


+ 19 - 6
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Camera.cs

@@ -56,15 +56,28 @@ namespace Max2Babylon
             babylonCamera.applyGravity = cameraNode.MaxNode.GetBoolProperty("babylonjs_applygravity");
             babylonCamera.ellipsoid = cameraNode.MaxNode.GetVector3Property("babylonjs_ellipsoid");
 
-            // Position
-            var wm = cameraNode.GetLocalTM(0);
+            // Position / rotation
+            var localTM = cameraNode.GetObjectTM(0);
             if (cameraNode.NodeParent != null)
             {
                 var parentWorld = cameraNode.NodeParent.GetObjectTM(0);
-                wm.MultiplyBy(parentWorld.Inverse);
+                localTM.MultiplyBy(parentWorld.Inverse);
+            }
+
+            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)
+            {
+                babylonCamera.rotationQuaternion = new[] { rotation.X, rotation.Y, rotation.Z, -rotation.W };
+            }
+            else
+            {
+                babylonCamera.rotation = QuaternionToEulerAngles(rotation);
             }
-            var position = wm.Translation;
-            babylonCamera.position = new [] { position.X, position.Y, position.Z };
 
             // Target
             var target = gameCamera.CameraTarget;
@@ -74,7 +87,7 @@ namespace Max2Babylon
             }
             else
             {
-                var dir = wm.GetRow(3);
+                var dir = localTM.GetRow(3);
                 babylonCamera.target = new [] { position.X - dir.X, position.Y - dir.Y, position.Z - dir.Z };
             }
 

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

@@ -84,14 +84,14 @@ namespace Max2Babylon
             return parentId;
         }
 
-        private void RotationToEulerAngles(BabylonAbstractMesh babylonMesh, IQuat rotation)
+        private float[] QuaternionToEulerAngles(IQuat rotation)
         {
             float rotx = 0, roty = 0, rotz = 0;
             unsafe
             {
                 rotation.GetEuler(new IntPtr(&rotx), new IntPtr(&roty), new IntPtr(&rotz));
             }
-            babylonMesh.rotation = new[] { rotx, roty, rotz };
+            return new[] { rotx, roty, rotz };
         }
 
         private int bonesCount;
@@ -226,7 +226,7 @@ namespace Max2Babylon
             }
             else
             {
-                RotationToEulerAngles(babylonMesh, meshRotation);
+                babylonMesh.rotation = QuaternionToEulerAngles(meshRotation);
             }
 
             babylonMesh.scaling = new[] { meshScale.X, meshScale.Y, meshScale.Z };
@@ -487,7 +487,7 @@ namespace Max2Babylon
                     }
                     else
                     {
-                        RotationToEulerAngles(instance, instanceRotation);
+                        instance.rotation = QuaternionToEulerAngles(instanceRotation);
                     }
 
                     instance.scaling = new[] { instanceScale.X, instanceScale.Y, instanceScale.Z };

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

@@ -28,7 +28,6 @@
         /// </summary>
         private void InitializeComponent()
         {
-            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ExporterForm));
             this.butExport = new System.Windows.Forms.Button();
             this.label1 = new System.Windows.Forms.Label();
             this.txtFilename = new System.Windows.Forms.TextBox();
@@ -138,7 +137,7 @@
             // pictureBox2
             // 
             this.pictureBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
-            this.pictureBox2.Image = ((System.Drawing.Image)(resources.GetObject("pictureBox2.Image")));
+            this.pictureBox2.Image = global::Max2Babylon.Properties.Resources.Logo_Exporter_v3;
             this.pictureBox2.Location = new System.Drawing.Point(511, 12);
             this.pictureBox2.Name = "pictureBox2";
             this.pictureBox2.Size = new System.Drawing.Size(300, 130);
@@ -272,7 +271,7 @@
             this.chkGltf.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
             this.chkGltf.Location = new System.Drawing.Point(30, 133);
             this.chkGltf.Name = "chkGltf";
-            this.chkGltf.Size = new System.Drawing.Size(135, 17);
+            this.chkGltf.Size = new System.Drawing.Size(107, 17);
             this.chkGltf.TabIndex = 17;
             this.chkGltf.Text = "Generate glTF file";
             this.chkGltf.UseVisualStyleBackColor = true;

+ 0 - 197
Exporters/3ds Max/Max2Babylon/Forms/ExporterForm.resx

@@ -120,201 +120,4 @@
   <metadata name="saveFileDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
-  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
-  <data name="pictureBox2.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
-    <value>
-        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
-</value>
-  </data>
 </root>