Bladeren bron

New version of Max2Babylon with smoothing groups and new optimization algorithm
babylon.js:
- TGA and DDS are now supported by sandbox
- 1.12 release candidate

David Catuhe 11 jaren geleden
bovenliggende
commit
9b8337cf4f
27 gewijzigde bestanden met toevoegingen van 1040 en 173 verwijderingen
  1. 2 4
      Babylon/Cameras/babylon.arcRotateCamera.js
  2. 3 5
      Babylon/Cameras/babylon.arcRotateCamera.ts
  3. 14 5
      Babylon/Tools/babylon.filesInput.js
  4. 16 8
      Babylon/Tools/babylon.filesInput.ts
  5. 16 8
      Babylon/Tools/babylon.tools.js
  6. 18 7
      Babylon/Tools/babylon.tools.ts
  7. BIN
      Exporters/3ds Max/Max2Babylon-0.4.7.zip
  8. 3 3
      Exporters/3ds Max/Max2Babylon/BabylonPropertiesActionItem.cs
  9. 143 3
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Animation.cs
  10. 15 9
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Camera.cs
  11. 27 14
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Light.cs
  12. 90 37
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Mesh.cs
  13. 24 9
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.cs
  14. 7 0
      Exporters/3ds Max/Max2Babylon/Exporter/GlobalVertex.cs
  15. 37 6
      Exporters/3ds Max/Max2Babylon/Forms/CameraPropertiesForm.Designer.cs
  16. 3 1
      Exporters/3ds Max/Max2Babylon/Forms/CameraPropertiesForm.cs
  17. 5 4
      Exporters/3ds Max/Max2Babylon/Forms/ExporterForm.cs
  18. 35 4
      Exporters/3ds Max/Max2Babylon/Forms/LightPropertiesForm.Designer.cs
  19. 3 1
      Exporters/3ds Max/Max2Babylon/Forms/LightPropertiesForm.cs
  20. 36 21
      Exporters/3ds Max/Max2Babylon/Forms/ObjectPropertiesForm.Designer.cs
  21. 6 4
      Exporters/3ds Max/Max2Babylon/Forms/ObjectPropertiesForm.cs
  22. 30 7
      Exporters/3ds Max/Max2Babylon/Max2Babylon.csproj
  23. 413 0
      Exporters/3ds Max/Max2Babylon/Tools/Tools.cs
  24. 84 0
      Exporters/3ds Max/Max2Babylon/Tools/VNormal.cs
  25. 0 4
      Exporters/3ds Max/Max2Babylon/packages.config
  26. 1 0
      Exporters/3ds Max/readme.md
  27. 9 9
      babylon.1.12-beta.js

+ 2 - 4
Babylon/Cameras/babylon.arcRotateCamera.js

@@ -345,10 +345,8 @@ var BABYLON;
             this.radius = radiusv3.length();
 
             // Alpha
-            this.alpha = Math.acos(radiusv3.x / Math.sqrt(
-                Math.pow(radiusv3.x, 2) +
-                Math.pow(radiusv3.z, 2)
-            ));
+            this.alpha = Math.acos(radiusv3.x / Math.sqrt(Math.pow(radiusv3.x, 2) + Math.pow(radiusv3.z, 2)));
+
             if (radiusv3.z < 0) {
                 this.alpha = 2 * Math.PI - this.alpha;
             }

+ 3 - 5
Babylon/Cameras/babylon.arcRotateCamera.ts

@@ -363,10 +363,8 @@
             this.radius = radiusv3.length();
 
             // Alpha
-            this.alpha = Math.acos(radiusv3.x / Math.sqrt(
-                Math.pow(radiusv3.x, 2) +
-                Math.pow(radiusv3.z, 2)
-            ));
+            this.alpha = Math.acos(radiusv3.x / Math.sqrt(Math.pow(radiusv3.x, 2) + Math.pow(radiusv3.z, 2)));
+
             if (radiusv3.z < 0) {
                 this.alpha = 2 * Math.PI - this.alpha;
             }
@@ -420,4 +418,4 @@
             this.maxZ = distance * 2;
         }
     }
-} 
+} 

+ 14 - 5
Babylon/Tools/babylon.filesInput.js

@@ -80,12 +80,20 @@ var BABYLON;
 
             if (filesToLoad && filesToLoad.length > 0) {
                 for (var i = 0; i < filesToLoad.length; i++) {
-                    if (filesToLoad[i].name.indexOf(".babylon") !== -1 && filesToLoad[i].name.indexOf(".manifest") === -1 && filesToLoad[i].name.indexOf(".incremental") === -1 && filesToLoad[i].name.indexOf(".babylonmeshdata") === -1 && filesToLoad[i].name.indexOf(".babylongeometrydata") === -1) {
-                        sceneFileToLoad = filesToLoad[i];
-                    } else {
-                        if (filesToLoad[i].type.indexOf("image/jpeg") == 0 || filesToLoad[i].type.indexOf("image/png") == 0) {
+                    switch (filesToLoad[i].type) {
+                        case "image/jpeg":
+                        case "image/png":
                             BABYLON.FilesInput.FilesTextures[filesToLoad[i].name] = filesToLoad[i];
-                        }
+                            break;
+                        case "image/targa":
+                        case "image/vnd.ms-dds":
+                            BABYLON.FilesInput.FilesToLoad[filesToLoad[i].name] = filesToLoad[i];
+                            break;
+                        default:
+                            if (filesToLoad[i].name.indexOf(".babylon") !== -1 && filesToLoad[i].name.indexOf(".manifest") === -1 && filesToLoad[i].name.indexOf(".incremental") === -1 && filesToLoad[i].name.indexOf(".babylonmeshdata") === -1 && filesToLoad[i].name.indexOf(".babylongeometrydata") === -1) {
+                                sceneFileToLoad = filesToLoad[i];
+                            }
+                            break;
                     }
                 }
 
@@ -123,6 +131,7 @@ var BABYLON;
             }
         };
         FilesInput.FilesTextures = new Array();
+        FilesInput.FilesToLoad = new Array();
         return FilesInput;
     })();
     BABYLON.FilesInput = FilesInput;

+ 16 - 8
Babylon/Tools/babylon.filesInput.ts

@@ -17,6 +17,7 @@ module BABYLON {
         private startingProcessingFilesCallback;
         private elementToMonitor: HTMLElement;
         public static FilesTextures: any[] = new Array();
+        public static FilesToLoad: any[] = new Array();
 
         /// Register to core BabylonJS object: engine, scene, rendering canvas, callback function when the scene will be loaded,
         /// loading progress callback and optionnal addionnal logic to call in the rendering loop
@@ -89,15 +90,22 @@ module BABYLON {
 
             if (filesToLoad && filesToLoad.length > 0) {
                 for (var i = 0; i < filesToLoad.length; i++) {
-                    if (filesToLoad[i].name.indexOf(".babylon") !== -1 && filesToLoad[i].name.indexOf(".manifest") === -1
-                        && filesToLoad[i].name.indexOf(".incremental") === -1 && filesToLoad[i].name.indexOf(".babylonmeshdata") === -1
-                        && filesToLoad[i].name.indexOf(".babylongeometrydata") === -1) {
-                        sceneFileToLoad = filesToLoad[i];
-                    }
-                    else {
-                        if (filesToLoad[i].type.indexOf("image/jpeg") == 0 || filesToLoad[i].type.indexOf("image/png") == 0) {
+                    switch (filesToLoad[i].type) {
+                        case "image/jpeg":
+                        case "image/png":
                             BABYLON.FilesInput.FilesTextures[filesToLoad[i].name] = filesToLoad[i];
-                        }
+                            break;
+                        case "image/targa":
+                        case "image/vnd.ms-dds":
+                            BABYLON.FilesInput.FilesToLoad[filesToLoad[i].name] = filesToLoad[i];
+                            break;
+                        default:
+                            if (filesToLoad[i].name.indexOf(".babylon") !== -1 && filesToLoad[i].name.indexOf(".manifest") === -1
+                                && filesToLoad[i].name.indexOf(".incremental") === -1 && filesToLoad[i].name.indexOf(".babylonmeshdata") === -1
+                                && filesToLoad[i].name.indexOf(".babylongeometrydata") === -1) {
+                                sceneFileToLoad = filesToLoad[i];
+                            }
+                            break;
                     }
                 }
 

+ 16 - 8
Babylon/Tools/babylon.tools.js

@@ -240,23 +240,31 @@ var BABYLON;
                 database.loadSceneFromDB(url, callback, progressCallBack, noIndexedDB);
             };
 
-            // Caching only scenes files
-            if (database && url.indexOf(".babylon") !== -1 && (database.enableSceneOffline)) {
-                database.openAsync(loadFromIndexedDB, noIndexedDB);
+            if (url.indexOf("file:") !== -1) {
+                var fileName = url.substring(5);
+                BABYLON.Tools.ReadFile(BABYLON.FilesInput.FilesToLoad[fileName], callback, progressCallBack, true);
             } else {
-                noIndexedDB();
+                // Caching only scenes files
+                if (database && url.indexOf(".babylon") !== -1 && (database.enableSceneOffline)) {
+                    database.openAsync(loadFromIndexedDB, noIndexedDB);
+                } else {
+                    noIndexedDB();
+                }
             }
         };
 
-        Tools.ReadFile = function (fileToLoad, callback, progressCallBack) {
+        Tools.ReadFile = function (fileToLoad, callback, progressCallBack, useArrayBuffer) {
             var reader = new FileReader();
             reader.onload = function (e) {
                 callback(e.target.result);
             };
             reader.onprogress = progressCallBack;
-
-            // Asynchronous read
-            reader.readAsText(fileToLoad);
+            if (!useArrayBuffer) {
+                // Asynchronous read
+                reader.readAsText(fileToLoad);
+            } else {
+                reader.readAsArrayBuffer(fileToLoad);
+            }
         };
 
         // Misc.

+ 18 - 7
Babylon/Tools/babylon.tools.ts

@@ -263,23 +263,34 @@ module BABYLON {
                 database.loadSceneFromDB(url, callback, progressCallBack, noIndexedDB);
             };
 
-            // Caching only scenes files
-            if (database && url.indexOf(".babylon") !== -1 && (database.enableSceneOffline)) {
-                database.openAsync(loadFromIndexedDB, noIndexedDB);
+            if (url.indexOf("file:") !== -1) {
+                var fileName = url.substring(5);
+                BABYLON.Tools.ReadFile(BABYLON.FilesInput.FilesToLoad[fileName], callback, progressCallBack, true);
             }
             else {
-                noIndexedDB();
+                // Caching only scenes files
+                if (database && url.indexOf(".babylon") !== -1 && (database.enableSceneOffline)) {
+                    database.openAsync(loadFromIndexedDB, noIndexedDB);
+                }
+                else {
+                    noIndexedDB();
+                }
             }
         }
 
-        public static ReadFile(fileToLoad, callback, progressCallBack): void {
+        public static ReadFile(fileToLoad, callback, progressCallBack, useArrayBuffer?: boolean): void {
             var reader = new FileReader();
             reader.onload = e => {
                 callback(e.target.result);
             };
             reader.onprogress = progressCallBack;
-            // Asynchronous read
-            reader.readAsText(fileToLoad);
+            if (!useArrayBuffer) {
+                // Asynchronous read
+                reader.readAsText(fileToLoad);
+            }
+            else {
+                reader.readAsArrayBuffer(fileToLoad);
+            }
         }
 
         // Misc.        

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


+ 3 - 3
Exporters/3ds Max/Max2Babylon/BabylonPropertiesActionItem.cs

@@ -18,7 +18,7 @@ namespace Max2Babylon
 
             var firstNode = Loader.Core.GetSelNode(0);
 
-            if (firstNode.ObjectRef != null && firstNode.ObjectRef.SuperClassID == SClass_ID.Camera)
+            if (firstNode.ObjectRef != null && firstNode.ObjectRef.Eval(0).Obj.SuperClassID == SClass_ID.Camera)
             {
                 using (var frm = new CameraPropertiesForm())
                 {
@@ -27,7 +27,7 @@ namespace Max2Babylon
                 }
             }
 
-            if (firstNode.ObjectRef != null && firstNode.ObjectRef.SuperClassID == SClass_ID.Geomobject)
+            if (firstNode.ObjectRef != null && firstNode.ObjectRef.Eval(0).Obj.SuperClassID == SClass_ID.Geomobject)
             {
                 using (var frm = new ObjectPropertiesForm())
                 {
@@ -36,7 +36,7 @@ namespace Max2Babylon
                 }
             }
 
-            if (firstNode.ObjectRef != null && firstNode.ObjectRef.SuperClassID == SClass_ID.Light)
+            if (firstNode.ObjectRef != null && firstNode.ObjectRef.Eval(0).Obj.SuperClassID == SClass_ID.Light)
             {
                 using (var frm = new LightPropertiesForm())
                 {

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

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using Autodesk.Max;
 using BabylonExport.Entities;
 using MaxSharp;
 
@@ -7,6 +8,146 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
+        const int Ticks = 160;
+
+        private BabylonAnimationKey GenerateFloatFunc(int index, IIKeyControl keyControl)
+        {
+            var key = Loader.Global.ILinFloatKey.Create();
+            keyControl.GetKey(index, key);
+
+            return new BabylonAnimationKey
+            {
+                frame = key.Time / Ticks,
+                values = new []{key.Val}
+            };
+        }
+
+        private bool ExportFloatController(IControl control, string property, List<BabylonAnimation> animations)
+        {
+            return ExportController(control, property, animations, 0x2001, BabylonAnimation.DataType.Float, GenerateFloatFunc);
+        }
+
+        private bool ExportQuaternionController(IControl control, string property, List<BabylonAnimation> animations)
+        {
+            IQuat previousQuat = null;
+
+            return ExportController(control, property, animations, 0x2003, BabylonAnimation.DataType.Quaternion,
+                (index, keyControl) =>
+                {
+                    var key = Loader.Global.ILinRotKey.Create();
+                    keyControl.GetKey(index, key);
+                    var newQuat = key.Val;
+
+                    if (index > 0)
+                    {
+                        newQuat = previousQuat.Multiply(newQuat);
+                    }
+
+                    previousQuat = newQuat;
+
+                    return new BabylonAnimationKey
+                    {
+                        frame = key.Time / Ticks,
+                        values = newQuat.ToArray()
+                    };
+                });
+        }
+
+        private bool ExportVector3Controller(IControl control, string property, List<BabylonAnimation> animations)
+        {
+            var result = false;
+
+            if (control.XController != null || control.YController != null || control.ZController != null)
+            {
+                result |= ExportFloatController(control.XController, property + ".x", animations);
+                result |= ExportFloatController(control.ZController, property + ".y", animations);
+                result |= ExportFloatController(control.YController, property + ".z", animations);
+
+                return result;
+            }
+
+            if (ExportController(control, property, animations, 0x2002, BabylonAnimation.DataType.Vector3,
+                (index, keyControl) =>
+                {
+                    var key = Loader.Global.ILinPoint3Key.Create();
+                    keyControl.GetKey(index, key);
+
+                    return new BabylonAnimationKey
+                    {
+                        frame = key.Time/Ticks,
+                        values = key.Val.ToArraySwitched()
+                    };
+                }))
+            {
+                return true;
+            }
+
+            return ExportController(control, property, animations, 0x2004, BabylonAnimation.DataType.Vector3,
+                (index, keyControl) =>
+                {
+                    var key = Loader.Global.ILinScaleKey.Create();
+                    keyControl.GetKey(index, key);
+
+                    return new BabylonAnimationKey
+                    {
+                        frame = key.Time/Ticks,
+                        values = key.Val.S.ToArraySwitched()
+                    };
+                });
+        }
+
+        private bool ExportController(IControl control, string property, List<BabylonAnimation> animations, uint classId, BabylonAnimation.DataType dataType, Func<int, IIKeyControl, BabylonAnimationKey> generateFunc)
+        {
+            if (control == null)
+            {
+                return false;
+            }
+
+            var keyControl = control.GetInterface(InterfaceID.Keycontrol) as IIKeyControl;
+
+            if (keyControl == null)
+            {
+                return false;
+            }
+
+            if (control.ClassID.PartA != classId)
+            {
+                return false;
+            }
+
+            var keys = new List<BabylonAnimationKey>();
+            BabylonAnimation.LoopBehavior loopBehavior;
+
+            switch (control.GetORT(2))
+            {
+                case 2:
+                    loopBehavior = BabylonAnimation.LoopBehavior.Cycle;
+                    break;
+                default:
+                    loopBehavior = BabylonAnimation.LoopBehavior.Relative;
+                    break;
+            }
+
+            for (var index = 0; index < keyControl.NumKeys; index++)
+            {
+                keys.Add(generateFunc(index, keyControl));
+            }
+
+            var babylonAnimation = new BabylonAnimation
+            {
+                dataType = dataType,
+                name = property + " animation",
+                keys = keys.ToArray(),
+                framePerSecond = Loader.Global.FrameRate,
+                loopBehavior = loopBehavior,
+                property = property
+            };
+
+            animations.Add(babylonAnimation);
+
+            return true;
+        }
+
         private void ExportVector3Animation(string property, List<BabylonAnimation> animations,
             Func<int, float[]> extractValueFunc)
         {
@@ -27,13 +168,12 @@ namespace Max2Babylon
 
         private void ExportAnimation(string property, List<BabylonAnimation> animations, Func<int, float[]> extractValueFunc, BabylonAnimation.DataType dataType)
         {
-            const int ticks = 160;
             var start = Loader.Core.AnimRange.Start;
             var end = Loader.Core.AnimRange.End;
 
             float[] previous = null;
             var keys = new List<BabylonAnimationKey>();
-            for (var key = start; key <= end; key += ticks)
+            for (var key = start; key <= end; key += Ticks)
             {
                 var current = extractValueFunc(key);
 
@@ -41,7 +181,7 @@ namespace Max2Babylon
                 {
                     keys.Add(new BabylonAnimationKey()
                     {
-                        frame = key / ticks,
+                        frame = key / Ticks,
                         values = current
                     });
                 }

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

@@ -1,5 +1,4 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using BabylonExport.Entities;
 using MaxSharp;
 
@@ -7,8 +6,13 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private BabylonCamera ExportCamera(Node cameraNode, BabylonScene babylonScene)
+        private void ExportCamera(Node cameraNode, BabylonScene babylonScene)
         {
+            if (cameraNode._Node.GetBoolProperty("babylonjs_noexport"))
+            {
+                return;
+            }
+
             var maxCamera = (cameraNode.Object as Camera)._Camera;
             var babylonCamera = new BabylonCamera();
 
@@ -57,13 +61,16 @@ namespace Max2Babylon
 
             // Animations
             var animations = new List<BabylonAnimation>();
-            ExportVector3Animation("position", animations, key =>
+            if (!ExportVector3Controller(cameraNode._Node.TMController.PositionController, "position", animations))
             {
-                var worldMatrix = cameraNode.GetWorldMatrix(key, cameraNode.HasParent());
-                return worldMatrix.Trans.ToArraySwitched();
-            });
+                ExportVector3Animation("position", animations, key =>
+                {
+                    var worldMatrix = cameraNode.GetWorldMatrix(key, cameraNode.HasParent());
+                    return worldMatrix.Trans.ToArraySwitched();
+                });
+            }
 
-            ExportFloatAnimation("fov", animations, key => new[] { Tools.ConvertFov(maxCamera.GetFOV(key, Interval.Forever._IInterval)) });
+            ExportFloatAnimation("fov", animations, key => new[] {Tools.ConvertFov(maxCamera.GetFOV(key, Interval.Forever._IInterval))});
 
             babylonCamera.animations = animations.ToArray();
 
@@ -76,7 +83,6 @@ namespace Max2Babylon
             }
 
             babylonScene.CamerasList.Add(babylonCamera);
-            return babylonCamera;
         }
     }
 }

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

@@ -8,8 +8,13 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private BabylonLight ExportLight(Node lightNode, BabylonScene babylonScene)
+        private void ExportLight(Node lightNode, BabylonScene babylonScene)
         {
+            if (lightNode._Node.GetBoolProperty("babylonjs_noexport"))
+            {
+                return;
+            }
+
             var maxLight = (lightNode.Object as Light);
             var babylonLight = new BabylonLight();
 
@@ -34,13 +39,6 @@ namespace Max2Babylon
                     break;
                 case LightType.DirectLgt:
                     babylonLight.type = 1;
-
-                    // Shadows
-                    if (maxLight.ShadowMethod == 1)
-                    {
-                        ExportShadowGenerator(lightNode, babylonScene);
-                    }
-
                     break;
                 case LightType.AmbientLgt:
                     babylonLight.type = 3;
@@ -49,6 +47,19 @@ namespace Max2Babylon
                     break;
             }
 
+            // Shadows
+            if (maxLight.ShadowMethod == 1)
+            {
+                if (lightState.Type == LightType.DirectLgt)
+                {
+                    ExportShadowGenerator(lightNode, babylonScene);
+                }
+                else
+                {
+                    RaiseWarning("Shadows maps are only supported for directional lights", true);
+                }
+            }
+
             // Position
             var wm = lightNode.GetWorldMatrix(0, false);
             var position = wm.Trans;
@@ -108,11 +119,15 @@ namespace Max2Babylon
 
             // Animations
             var animations = new List<BabylonAnimation>();
-            ExportVector3Animation("position", animations, key =>
+
+            if (!ExportVector3Controller(lightNode._Node.TMController.PositionController, "position", animations))
             {
-                var worldMatrix = lightNode.GetWorldMatrix(key, lightNode.HasParent());
-                return worldMatrix.Trans.ToArraySwitched();
-            });
+                ExportVector3Animation("position", animations, key =>
+                {
+                    var worldMatrix = lightNode.GetWorldMatrix(key, lightNode.HasParent());
+                    return worldMatrix.Trans.ToArraySwitched();
+                });
+            }
 
             ExportVector3Animation("direction", animations, key =>
             {
@@ -132,7 +147,6 @@ namespace Max2Babylon
 
             ExportFloatAnimation("intensity", animations, key => new[] { maxLight.GetIntensity(key, Interval.Forever) });
 
-
             babylonLight.animations = animations.ToArray();
 
             if (lightNode._Node.GetBoolProperty("babylonjs_autoanimate"))
@@ -144,7 +158,6 @@ namespace Max2Babylon
             }
 
             babylonScene.LightsList.Add(babylonLight);
-            return babylonLight;
         }
     }
 }

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

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Runtime.InteropServices;
 using System.Threading;
 using Autodesk.Max;
 using BabylonExport.Entities;
@@ -11,12 +10,16 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private BabylonMesh ExportMesh(Node meshNode, BabylonScene babylonScene, CancellationToken token)
+        private void ExportMesh(Node meshNode, BabylonScene babylonScene, CancellationToken token)
         {
+            if (meshNode._Node.GetBoolProperty("babylonjs_noexport"))
+            {
+                return;
+            }
+
             var babylonMesh = new BabylonMesh();
             int vx1, vx2, vx3;
-
-            RaiseMessage(meshNode.Name, true);
+           
             babylonMesh.name = meshNode.Name;
             babylonMesh.id = meshNode.GetGuid().ToString();
             if (meshNode.HasParent())
@@ -85,9 +88,13 @@ namespace Max2Babylon
 
             // Mesh
             var objectState = meshNode._Node.EvalWorldState(0, false);
-            var mesh = objectState.Obj.GetMesh();
+            bool mustBeDeleted;
+            var triObject = objectState.Obj.GetMesh(out mustBeDeleted);
+            var mesh = triObject != null ? triObject.Mesh : null;
             var computedMesh = meshNode.GetMesh();
 
+            RaiseMessage(meshNode.Name, mesh == null ? System.Drawing.Color.Gray : System.Drawing.Color.Black, true);
+
             if (mesh != null)
             {
                 mesh.BuildNormals();
@@ -132,13 +139,26 @@ namespace Max2Babylon
                 var hasUV = mesh.NumTVerts > 0;
                 var hasUV2 = mesh.GetNumMapVerts(2) > 0;
 
-                var noOptimize = meshNode._Node.GetBoolProperty("babylonjs_nooptimize");
+                var optimizeVertices = meshNode._Node.GetBoolProperty("babylonjs_optimizevertices");
+
+                // Compute normals
+                VNormal[] vnorms = null;
+                List<GlobalVertex>[] verticesAlreadyExported = null;
+
+                if (!optimizeVertices)
+                {
+                    vnorms = Tools.ComputeNormals(mesh);
+                }
+                else
+                {
+                    verticesAlreadyExported = new List<GlobalVertex>[mesh.NumVerts];
+                }
 
                 for (var face = 0; face < mesh.NumFaces; face++)
                 {
-                    indices.Add(CreateGlobalVertex(mesh, computedMesh, face, vx1, vertices, hasUV, hasUV2, noOptimize));
-                    indices.Add(CreateGlobalVertex(mesh, computedMesh, face, vx2, vertices, hasUV, hasUV2, noOptimize));
-                    indices.Add(CreateGlobalVertex(mesh, computedMesh, face, vx3, vertices, hasUV, hasUV2, noOptimize));
+                    indices.Add(CreateGlobalVertex(mesh, computedMesh, face, vx1, vertices, hasUV, hasUV2, vnorms, verticesAlreadyExported));
+                    indices.Add(CreateGlobalVertex(mesh, computedMesh, face, vx2, vertices, hasUV, hasUV2, vnorms, verticesAlreadyExported));
+                    indices.Add(CreateGlobalVertex(mesh, computedMesh, face, vx3, vertices, hasUV, hasUV2, vnorms, verticesAlreadyExported));
                     matIDs.Add(mesh.Faces[face].MatID % multiMatsCount);
                     if (token.IsCancellationRequested) token.ThrowIfCancellationRequested();
                 }
@@ -146,8 +166,15 @@ namespace Max2Babylon
                 if (vertices.Count >= 65536)
                 {
                     RaiseError(string.Format("Mesh {0} has too many vertices: {1} (limit is 65535)", babylonMesh.name, vertices.Count));
+
+                    if (!optimizeVertices)
+                    {
+                        RaiseError("You can try to optimize your object using [Try to optimize vertices] option");
+                    }
                 }
 
+                RaiseMessage(string.Format("{0} vertices, {1} faces", vertices.Count, indices.Count / 3), true, false, true);
+
                 // Buffers
                 babylonMesh.positions = vertices.SelectMany(v => v.Position.ToArraySwitched()).ToArray();
                 babylonMesh.normals = vertices.SelectMany(v => v.Normal.ToArraySwitched()).ToArray();
@@ -236,39 +263,56 @@ namespace Max2Babylon
 
                 // Buffers - Indices
                 babylonMesh.indices = sortedIndices.ToArray();
+
+                if (mustBeDeleted)
+                {
+                    triObject.DeleteMe();
+                }
             }
 
 
             // Animations
             var animations = new List<BabylonAnimation>();
-            ExportVector3Animation("position", animations, key =>
+
+            if (!ExportVector3Controller(meshNode._Node.TMController.PositionController, "position", animations))
             {
-                var worldMatrix = meshNode.GetWorldMatrix(key, meshNode.HasParent());
-                return worldMatrix.Trans.ToArraySwitched();
-            });
+                ExportVector3Animation("position", animations, key =>
+                {
+                    var worldMatrix = meshNode.GetWorldMatrix(key, meshNode.HasParent());
+                    return worldMatrix.Trans.ToArraySwitched();
+                });
+            }
 
-            ExportQuaternionAnimation("rotationQuaternion", animations, key =>
+            if (!ExportQuaternionController(meshNode._Node.TMController.RotationController, "rotationQuaternion", animations))
             {
-                var worldMatrix = meshNode.GetWorldMatrix(key, meshNode.HasParent());
+                ExportQuaternionAnimation("rotationQuaternion", animations, key =>
+                {
+                    var worldMatrix = meshNode.GetWorldMatrix(key, meshNode.HasParent());
 
-                var affineParts = Loader.Global.AffineParts.Create();
-                Loader.Global.DecompAffine(worldMatrix, affineParts);
+                    var affineParts = Loader.Global.AffineParts.Create();
+                    Loader.Global.DecompAffine(worldMatrix, affineParts);
 
-                return affineParts.Q.ToArray();
-            });
+                    return affineParts.Q.ToArray();
+                });
+            }
 
-            ExportVector3Animation("scaling", animations, key =>
+            if (!ExportVector3Controller(meshNode._Node.TMController.ScaleController, "scaling", animations))
             {
-                var worldMatrix = meshNode.GetWorldMatrix(key, meshNode.HasParent());
-
-                var affineParts = Loader.Global.AffineParts.Create();
-                Loader.Global.DecompAffine(worldMatrix, affineParts);
+                ExportVector3Animation("scaling", animations, key =>
+                {
+                    var worldMatrix = meshNode.GetWorldMatrix(key, meshNode.HasParent());
 
-                return affineParts.K.ToArraySwitched();
-            });
+                    var affineParts = Loader.Global.AffineParts.Create();
+                    Loader.Global.DecompAffine(worldMatrix, affineParts);
 
-            ExportFloatAnimation("visibility", animations, key => new []{meshNode._Node.GetVisibility(key, Interval.Forever._IInterval)});
+                    return affineParts.K.ToArraySwitched();
+                });
+            }
 
+            if (!ExportFloatController(meshNode._Node.VisController, "visibility", animations))
+            {
+                ExportFloatAnimation("visibility", animations, key => new[] {meshNode._Node.GetVisibility(key, Interval.Forever._IInterval)});
+            }
 
             babylonMesh.animations = animations.ToArray();
 
@@ -281,19 +325,18 @@ namespace Max2Babylon
             }
 
             babylonScene.MeshesList.Add(babylonMesh);
-
-            return babylonMesh;
         }
 
-        int CreateGlobalVertex(IMesh mesh, Mesh computedMesh, int face, int facePart, List<GlobalVertex> vertices, bool hasUV, bool hasUV2, bool noOptimize)
+        int CreateGlobalVertex(IMesh mesh, Mesh computedMesh, int face, int facePart, List<GlobalVertex> vertices, bool hasUV, bool hasUV2, VNormal[] vnorms, List<GlobalVertex>[] verticesAlreadyExported)
         {
-            var vertexIndex = (int)mesh.Faces[face].V[facePart];
-
+            var faceObject = mesh.Faces[face];
+            var vertexIndex = (int)faceObject.V[facePart];
 
             var vertex = new GlobalVertex
             {
+                BaseIndex = vertexIndex,
                 Position = mesh.Verts[vertexIndex],
-                Normal = computedMesh.vnormals[vertexIndex]._IPoint3
+                Normal = (vnorms != null) ? vnorms[vertexIndex].GetNormal(faceObject.SmGroup) : computedMesh.vnormals[vertexIndex]._IPoint3
             };
 
             if (hasUV)
@@ -308,14 +351,24 @@ namespace Max2Babylon
                 vertex.UV2 = Loader.Global.Point2.Create(mesh.MapVerts(2)[tvertexIndex].X, mesh.MapVerts(2)[tvertexIndex].Y);
             }
 
-            if (!noOptimize)
+            if (verticesAlreadyExported != null)
             {
-                var index = vertices.IndexOf(vertex);
+                if (verticesAlreadyExported[vertexIndex] != null)
+                {
+                    var index = verticesAlreadyExported[vertexIndex].IndexOf(vertex);
 
-                if (index > -1)
+                    if (index > -1)
+                    {
+                        return verticesAlreadyExported[vertexIndex][index].CurrentIndex;
+                    }
+                }
+                else
                 {
-                    return index;
+                    verticesAlreadyExported[vertexIndex] = new List<GlobalVertex>();
                 }
+
+                vertex.CurrentIndex = vertices.Count;
+                verticesAlreadyExported[vertexIndex].Add(vertex);
             }
 
             vertices.Add(vertex);

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

@@ -2,8 +2,7 @@
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
-using System.Runtime.InteropServices;
-using System.Runtime.Serialization.Json;
+using System.Linq;
 using System.Text;
 using System.Threading;
 using Autodesk.Max;
@@ -11,6 +10,7 @@ using BabylonExport.Entities;
 using MaxSharp;
 using Newtonsoft.Json;
 using Animatable = MaxSharp.Animatable;
+using Color = System.Drawing.Color;
 
 namespace Max2Babylon
 {
@@ -18,7 +18,7 @@ namespace Max2Babylon
     {
         public event Action<int> OnImportProgressChanged;
         public event Action<string, bool> OnWarning;
-        public event Action<string, bool, bool, bool> OnMessage;
+        public event Action<string, bool, bool, bool, Color> OnMessage;
         public event Action<string, bool> OnError;
 
         readonly List<string> alreadyExportedTextures = new List<string>();
@@ -49,16 +49,21 @@ namespace Max2Babylon
 
         void RaiseMessage(string message, bool asChild = false, bool emphasis = false, bool embed = false)
         {
+            RaiseMessage(message, Color.Black, asChild, emphasis, embed);
+        }
+
+        void RaiseMessage(string message, Color color, bool asChild = false, bool emphasis = false, bool embed = false)
+        {
             if (OnMessage != null)
             {
-                OnMessage(message, asChild, emphasis, embed);
+                OnMessage(message, asChild, emphasis, embed, color);
             }
         }
 
         public void Export(string outputFile, CancellationToken token)
         {
             RaiseMessage("Exportation started");
-            ReportProgressChanged(25);
+            ReportProgressChanged(0);
             var babylonScene = new BabylonScene(Path.GetDirectoryName(outputFile));
             var maxScene = Kernel.Scene;
             alreadyExportedTextures.Clear();
@@ -83,11 +88,11 @@ namespace Max2Babylon
             RaiseMessage("Exporting cameras");
             foreach (var cameraNode in maxScene.NodesListBySuperClass(SuperClassID.Camera))
             {
-                var babylonCamera = ExportCamera(cameraNode, babylonScene);
+                ExportCamera(cameraNode, babylonScene);
 
-                if (mainCamera == null)
+                if (mainCamera == null && babylonScene.CamerasList.Count > 0)
                 {
-                    mainCamera = babylonCamera;
+                    mainCamera = babylonScene.CamerasList[0];
                     babylonScene.activeCameraID = mainCamera.id;
                     RaiseMessage("Active camera set to " + mainCamera.name, true, true);
                 }
@@ -122,10 +127,20 @@ namespace Max2Babylon
             }
 
             // Meshes
+            ReportProgressChanged(10);
             RaiseMessage("Exporting meshes");
-            foreach (var meshNode in maxScene.NodesListBySuperClass(SuperClassID.GeometricObject))
+            var meshes = maxScene.NodesListBySuperClasses(new[] {SuperClassID.GeometricObject, SuperClassID.Helper});
+            var progressionStep = 80.0f / meshes.Count();
+            var progression = 10.0f;
+            foreach (var meshNode in meshes)
             {
+                Tools.PreparePipeline(meshNode._Node, true);
                 ExportMesh(meshNode, babylonScene, token);
+                Tools.PreparePipeline(meshNode._Node, false);
+
+                progression += progressionStep;
+                ReportProgressChanged((int)progression);
+
                 if (token.IsCancellationRequested) token.ThrowIfCancellationRequested();
             }
 

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

@@ -4,6 +4,8 @@ namespace Max2Babylon
 {
     public struct GlobalVertex
     {
+        public int BaseIndex { get; set; }
+        public int CurrentIndex { get; set; }
         public IPoint3 Position { get; set; }
         public IPoint3 Normal { get; set; }
         public IPoint2 UV { get; set; }
@@ -23,6 +25,11 @@ namespace Max2Babylon
 
             var other = (GlobalVertex)obj;
 
+            if (other.BaseIndex != BaseIndex)
+            {
+                return false;
+            }
+
             if (!other.Position.IsAlmostEqualTo(Position, Tools.Epsilon))
             {
                 return false;

+ 37 - 6
Exporters/3ds Max/Max2Babylon/Forms/CameraPropertiesForm.Designer.cs

@@ -48,6 +48,8 @@
             this.nupFrom = new System.Windows.Forms.NumericUpDown();
             this.label5 = new System.Windows.Forms.Label();
             this.chkAutoAnimate = new System.Windows.Forms.CheckBox();
+            this.groupBox4 = new System.Windows.Forms.GroupBox();
+            this.chkNoExport = new System.Windows.Forms.CheckBox();
             this.groupBox1.SuspendLayout();
             this.groupBox2.SuspendLayout();
             ((System.ComponentModel.ISupportInitialize)(this.nupInertia)).BeginInit();
@@ -56,6 +58,7 @@
             this.grpAutoAnimate.SuspendLayout();
             ((System.ComponentModel.ISupportInitialize)(this.nupTo)).BeginInit();
             ((System.ComponentModel.ISupportInitialize)(this.nupFrom)).BeginInit();
+            this.groupBox4.SuspendLayout();
             this.SuspendLayout();
             // 
             // groupBox1
@@ -65,7 +68,7 @@
             this.groupBox1.Controls.Add(this.chkGravity);
             this.groupBox1.Controls.Add(this.chkCollisions);
             this.groupBox1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.groupBox1.Location = new System.Drawing.Point(12, 12);
+            this.groupBox1.Location = new System.Drawing.Point(12, 77);
             this.groupBox1.Name = "groupBox1";
             this.groupBox1.Size = new System.Drawing.Size(319, 138);
             this.groupBox1.TabIndex = 0;
@@ -118,7 +121,7 @@
             this.butOK.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
             this.butOK.DialogResult = System.Windows.Forms.DialogResult.OK;
             this.butOK.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.butOK.Location = new System.Drawing.Point(93, 471);
+            this.butOK.Location = new System.Drawing.Point(93, 538);
             this.butOK.Name = "butOK";
             this.butOK.Size = new System.Drawing.Size(75, 23);
             this.butOK.TabIndex = 1;
@@ -131,7 +134,7 @@
             this.butCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
             this.butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
             this.butCancel.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.butCancel.Location = new System.Drawing.Point(174, 471);
+            this.butCancel.Location = new System.Drawing.Point(174, 538);
             this.butCancel.Name = "butCancel";
             this.butCancel.Size = new System.Drawing.Size(75, 23);
             this.butCancel.TabIndex = 2;
@@ -145,7 +148,7 @@
             this.groupBox2.Controls.Add(this.label1);
             this.groupBox2.Controls.Add(this.nupSpeed);
             this.groupBox2.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.groupBox2.Location = new System.Drawing.Point(12, 156);
+            this.groupBox2.Location = new System.Drawing.Point(12, 221);
             this.groupBox2.Name = "groupBox2";
             this.groupBox2.Size = new System.Drawing.Size(319, 140);
             this.groupBox2.TabIndex = 3;
@@ -201,7 +204,7 @@
             this.groupBox3.Controls.Add(this.grpAutoAnimate);
             this.groupBox3.Controls.Add(this.chkAutoAnimate);
             this.groupBox3.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.groupBox3.Location = new System.Drawing.Point(12, 302);
+            this.groupBox3.Location = new System.Drawing.Point(12, 367);
             this.groupBox3.Name = "groupBox3";
             this.groupBox3.Size = new System.Drawing.Size(319, 156);
             this.groupBox3.TabIndex = 5;
@@ -289,13 +292,37 @@
             this.chkAutoAnimate.UseVisualStyleBackColor = true;
             this.chkAutoAnimate.CheckedChanged += new System.EventHandler(this.chkAutoAnimate_CheckedChanged);
             // 
+            // groupBox4
+            // 
+            this.groupBox4.Controls.Add(this.chkNoExport);
+            this.groupBox4.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+            this.groupBox4.Location = new System.Drawing.Point(12, 12);
+            this.groupBox4.Name = "groupBox4";
+            this.groupBox4.Size = new System.Drawing.Size(319, 59);
+            this.groupBox4.TabIndex = 6;
+            this.groupBox4.TabStop = false;
+            this.groupBox4.Text = "Misc.";
+            // 
+            // chkNoExport
+            // 
+            this.chkNoExport.AutoSize = true;
+            this.chkNoExport.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+            this.chkNoExport.Location = new System.Drawing.Point(21, 28);
+            this.chkNoExport.Name = "chkNoExport";
+            this.chkNoExport.Size = new System.Drawing.Size(87, 17);
+            this.chkNoExport.TabIndex = 4;
+            this.chkNoExport.Text = "Do not export";
+            this.chkNoExport.ThreeState = true;
+            this.chkNoExport.UseVisualStyleBackColor = true;
+            // 
             // CameraPropertiesForm
             // 
             this.AcceptButton = this.butOK;
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
             this.CancelButton = this.butCancel;
-            this.ClientSize = new System.Drawing.Size(343, 506);
+            this.ClientSize = new System.Drawing.Size(343, 573);
+            this.Controls.Add(this.groupBox4);
             this.Controls.Add(this.groupBox3);
             this.Controls.Add(this.groupBox2);
             this.Controls.Add(this.butCancel);
@@ -318,6 +345,8 @@
             this.grpAutoAnimate.PerformLayout();
             ((System.ComponentModel.ISupportInitialize)(this.nupTo)).EndInit();
             ((System.ComponentModel.ISupportInitialize)(this.nupFrom)).EndInit();
+            this.groupBox4.ResumeLayout(false);
+            this.groupBox4.PerformLayout();
             this.ResumeLayout(false);
 
         }
@@ -344,5 +373,7 @@
         private System.Windows.Forms.NumericUpDown nupFrom;
         private System.Windows.Forms.Label label5;
         private System.Windows.Forms.CheckBox chkAutoAnimate;
+        private System.Windows.Forms.GroupBox groupBox4;
+        private System.Windows.Forms.CheckBox chkNoExport;
     }
 }

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

@@ -20,12 +20,13 @@ namespace Max2Babylon
             {
                 var node = Loader.Core.GetSelNode(index);
 
-                if (node.ObjectRef != null && node.ObjectRef.SuperClassID == SClass_ID.Camera)
+                if (node.ObjectRef != null && node.ObjectRef.Eval(0).Obj.SuperClassID == SClass_ID.Camera)
                 {
                     cameras.Add(node);
                 }
             }
 
+            Tools.PrepareCheckBox(chkNoExport, cameras, "babylonjs_noexport");
             Tools.PrepareCheckBox(chkCollisions, cameras, "babylonjs_checkcollisions");
             Tools.PrepareCheckBox(chkGravity, cameras, "babylonjs_applygravity");
 
@@ -42,6 +43,7 @@ namespace Max2Babylon
 
         private void butOK_Click(object sender, EventArgs e)
         {
+            Tools.UpdateCheckBox(chkNoExport, cameras, "babylonjs_noexport");
             Tools.UpdateCheckBox(chkCollisions, cameras, "babylonjs_checkcollisions");
             Tools.UpdateCheckBox(chkGravity, cameras, "babylonjs_applygravity");
 

+ 5 - 4
Exporters/3ds Max/Max2Babylon/Forms/ExporterForm.cs

@@ -43,12 +43,12 @@ namespace Max2Babylon
 
             treeView.Nodes.Clear();
 
-            exporter.OnImportProgressChanged += progress => Invoke(new Action(() =>
+            exporter.OnImportProgressChanged += progress => BeginInvoke(new Action(() =>
             {
                 progressBar.Value = progress;
             }));
 
-            exporter.OnWarning += (warning, asChild) => Invoke(new Action(() =>
+            exporter.OnWarning += (warning, asChild) => BeginInvoke(new Action(() =>
             {
                 previousNode = new TreeNode(warning) { ForeColor = Color.Orange };
 
@@ -57,7 +57,7 @@ namespace Max2Babylon
                 previousNode.EnsureVisible();
             }));
 
-            exporter.OnError += (error, asChild) => Invoke(new Action(() =>
+            exporter.OnError += (error, asChild) => BeginInvoke(new Action(() =>
             {
                 previousNode = new TreeNode(error) { ForeColor = Color.Red };
 
@@ -66,11 +66,12 @@ namespace Max2Babylon
                 previousNode.EnsureVisible();
             }));
 
-            exporter.OnMessage += (message, asChild, emphasis, embed) => Invoke(new Action(() =>
+            exporter.OnMessage += (message, asChild, emphasis, embed, color) => BeginInvoke(new Action(() =>
             {
                 var oldPrevious = previousNode;
 
                 previousNode = new TreeNode(message);
+                previousNode.ForeColor = color;
 
                 if (emphasis)
                 {

+ 35 - 4
Exporters/3ds Max/Max2Babylon/Forms/LightPropertiesForm.Designer.cs

@@ -38,10 +38,13 @@
             this.nupFrom = new System.Windows.Forms.NumericUpDown();
             this.label5 = new System.Windows.Forms.Label();
             this.chkAutoAnimate = new System.Windows.Forms.CheckBox();
+            this.groupBox4 = new System.Windows.Forms.GroupBox();
+            this.chkNoExport = new System.Windows.Forms.CheckBox();
             this.groupBox3.SuspendLayout();
             this.grpAutoAnimate.SuspendLayout();
             ((System.ComponentModel.ISupportInitialize)(this.nupTo)).BeginInit();
             ((System.ComponentModel.ISupportInitialize)(this.nupFrom)).BeginInit();
+            this.groupBox4.SuspendLayout();
             this.SuspendLayout();
             // 
             // butOK
@@ -49,7 +52,7 @@
             this.butOK.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
             this.butOK.DialogResult = System.Windows.Forms.DialogResult.OK;
             this.butOK.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.butOK.Location = new System.Drawing.Point(93, 179);
+            this.butOK.Location = new System.Drawing.Point(93, 252);
             this.butOK.Name = "butOK";
             this.butOK.Size = new System.Drawing.Size(75, 23);
             this.butOK.TabIndex = 1;
@@ -62,7 +65,7 @@
             this.butCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
             this.butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
             this.butCancel.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.butCancel.Location = new System.Drawing.Point(174, 179);
+            this.butCancel.Location = new System.Drawing.Point(174, 252);
             this.butCancel.Name = "butCancel";
             this.butCancel.Size = new System.Drawing.Size(75, 23);
             this.butCancel.TabIndex = 2;
@@ -74,7 +77,7 @@
             this.groupBox3.Controls.Add(this.grpAutoAnimate);
             this.groupBox3.Controls.Add(this.chkAutoAnimate);
             this.groupBox3.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.groupBox3.Location = new System.Drawing.Point(12, 12);
+            this.groupBox3.Location = new System.Drawing.Point(12, 77);
             this.groupBox3.Name = "groupBox3";
             this.groupBox3.Size = new System.Drawing.Size(319, 156);
             this.groupBox3.TabIndex = 5;
@@ -162,13 +165,37 @@
             this.chkAutoAnimate.UseVisualStyleBackColor = true;
             this.chkAutoAnimate.CheckedChanged += new System.EventHandler(this.chkAutoAnimate_CheckedChanged);
             // 
+            // groupBox4
+            // 
+            this.groupBox4.Controls.Add(this.chkNoExport);
+            this.groupBox4.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+            this.groupBox4.Location = new System.Drawing.Point(12, 12);
+            this.groupBox4.Name = "groupBox4";
+            this.groupBox4.Size = new System.Drawing.Size(319, 59);
+            this.groupBox4.TabIndex = 7;
+            this.groupBox4.TabStop = false;
+            this.groupBox4.Text = "Misc.";
+            // 
+            // chkNoExport
+            // 
+            this.chkNoExport.AutoSize = true;
+            this.chkNoExport.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+            this.chkNoExport.Location = new System.Drawing.Point(21, 28);
+            this.chkNoExport.Name = "chkNoExport";
+            this.chkNoExport.Size = new System.Drawing.Size(87, 17);
+            this.chkNoExport.TabIndex = 4;
+            this.chkNoExport.Text = "Do not export";
+            this.chkNoExport.ThreeState = true;
+            this.chkNoExport.UseVisualStyleBackColor = true;
+            // 
             // LightPropertiesForm
             // 
             this.AcceptButton = this.butOK;
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
             this.CancelButton = this.butCancel;
-            this.ClientSize = new System.Drawing.Size(343, 214);
+            this.ClientSize = new System.Drawing.Size(343, 287);
+            this.Controls.Add(this.groupBox4);
             this.Controls.Add(this.groupBox3);
             this.Controls.Add(this.butCancel);
             this.Controls.Add(this.butOK);
@@ -183,6 +210,8 @@
             this.grpAutoAnimate.PerformLayout();
             ((System.ComponentModel.ISupportInitialize)(this.nupTo)).EndInit();
             ((System.ComponentModel.ISupportInitialize)(this.nupFrom)).EndInit();
+            this.groupBox4.ResumeLayout(false);
+            this.groupBox4.PerformLayout();
             this.ResumeLayout(false);
 
         }
@@ -199,5 +228,7 @@
         private System.Windows.Forms.NumericUpDown nupFrom;
         private System.Windows.Forms.Label label5;
         private System.Windows.Forms.CheckBox chkAutoAnimate;
+        private System.Windows.Forms.GroupBox groupBox4;
+        private System.Windows.Forms.CheckBox chkNoExport;
     }
 }

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

@@ -20,12 +20,13 @@ namespace Max2Babylon
             {
                 var node = Loader.Core.GetSelNode(index);
 
-                if (node.ObjectRef != null && node.ObjectRef.SuperClassID == SClass_ID.Light)
+                if (node.ObjectRef != null && node.ObjectRef.Eval(0).Obj.SuperClassID == SClass_ID.Light)
                 {
                     lights.Add(node);
                 }
             }
 
+            Tools.PrepareCheckBox(chkNoExport, lights, "babylonjs_noexport");
             Tools.PrepareCheckBox(chkAutoAnimate, lights, "babylonjs_autoanimate");
             Tools.PrepareCheckBox(chkLoop, lights, "babylonjs_autoanimateloop");
             Tools.PrepareNumericUpDown(nupFrom, lights, "babylonjs_autoanimate_from");
@@ -34,6 +35,7 @@ namespace Max2Babylon
 
         private void butOK_Click(object sender, EventArgs e)
         {
+            Tools.UpdateCheckBox(chkNoExport, lights, "babylonjs_noexport");
             Tools.UpdateCheckBox(chkAutoAnimate, lights, "babylonjs_autoanimate");
             Tools.UpdateCheckBox(chkLoop, lights, "babylonjs_autoanimateloop");
             Tools.UpdateNumericUpDown(nupFrom, lights, "babylonjs_autoanimate_from");

+ 36 - 21
Exporters/3ds Max/Max2Babylon/Forms/ObjectPropertiesForm.Designer.cs

@@ -33,9 +33,10 @@
             this.butCancel = new System.Windows.Forms.Button();
             this.butOK = new System.Windows.Forms.Button();
             this.groupBox2 = new System.Windows.Forms.GroupBox();
+            this.chkNoExport = new System.Windows.Forms.CheckBox();
             this.chkShowSubMeshesBoundingBox = new System.Windows.Forms.CheckBox();
             this.chkShowBoundingBox = new System.Windows.Forms.CheckBox();
-            this.chkNoOptimize = new System.Windows.Forms.CheckBox();
+            this.chkOptimize = new System.Windows.Forms.CheckBox();
             this.chkPickable = new System.Windows.Forms.CheckBox();
             this.groupBox3 = new System.Windows.Forms.GroupBox();
             this.grpAutoAnimate = new System.Windows.Forms.GroupBox();
@@ -81,7 +82,7 @@
             this.butCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
             this.butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
             this.butCancel.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.butCancel.Location = new System.Drawing.Point(174, 509);
+            this.butCancel.Location = new System.Drawing.Point(174, 419);
             this.butCancel.Name = "butCancel";
             this.butCancel.Size = new System.Drawing.Size(75, 23);
             this.butCancel.TabIndex = 6;
@@ -93,7 +94,7 @@
             this.butOK.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
             this.butOK.DialogResult = System.Windows.Forms.DialogResult.OK;
             this.butOK.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.butOK.Location = new System.Drawing.Point(93, 509);
+            this.butOK.Location = new System.Drawing.Point(93, 419);
             this.butOK.Name = "butOK";
             this.butOK.Size = new System.Drawing.Size(75, 23);
             this.butOK.TabIndex = 5;
@@ -103,23 +104,36 @@
             // 
             // groupBox2
             // 
+            this.groupBox2.Controls.Add(this.chkNoExport);
             this.groupBox2.Controls.Add(this.chkShowSubMeshesBoundingBox);
             this.groupBox2.Controls.Add(this.chkShowBoundingBox);
-            this.groupBox2.Controls.Add(this.chkNoOptimize);
+            this.groupBox2.Controls.Add(this.chkOptimize);
             this.groupBox2.Controls.Add(this.chkPickable);
             this.groupBox2.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
             this.groupBox2.Location = new System.Drawing.Point(12, 77);
             this.groupBox2.Name = "groupBox2";
-            this.groupBox2.Size = new System.Drawing.Size(319, 143);
+            this.groupBox2.Size = new System.Drawing.Size(319, 154);
             this.groupBox2.TabIndex = 2;
             this.groupBox2.TabStop = false;
             this.groupBox2.Text = "Misc.";
             // 
+            // chkNoExport
+            // 
+            this.chkNoExport.AutoSize = true;
+            this.chkNoExport.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+            this.chkNoExport.Location = new System.Drawing.Point(21, 28);
+            this.chkNoExport.Name = "chkNoExport";
+            this.chkNoExport.Size = new System.Drawing.Size(87, 17);
+            this.chkNoExport.TabIndex = 4;
+            this.chkNoExport.Text = "Do not export";
+            this.chkNoExport.ThreeState = true;
+            this.chkNoExport.UseVisualStyleBackColor = true;
+            // 
             // chkShowSubMeshesBoundingBox
             // 
             this.chkShowSubMeshesBoundingBox.AutoSize = true;
             this.chkShowSubMeshesBoundingBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.chkShowSubMeshesBoundingBox.Location = new System.Drawing.Point(21, 97);
+            this.chkShowSubMeshesBoundingBox.Location = new System.Drawing.Point(21, 120);
             this.chkShowSubMeshesBoundingBox.Name = "chkShowSubMeshesBoundingBox";
             this.chkShowSubMeshesBoundingBox.Size = new System.Drawing.Size(184, 17);
             this.chkShowSubMeshesBoundingBox.TabIndex = 3;
@@ -131,7 +145,7 @@
             // 
             this.chkShowBoundingBox.AutoSize = true;
             this.chkShowBoundingBox.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.chkShowBoundingBox.Location = new System.Drawing.Point(21, 74);
+            this.chkShowBoundingBox.Location = new System.Drawing.Point(21, 97);
             this.chkShowBoundingBox.Name = "chkShowBoundingBox";
             this.chkShowBoundingBox.Size = new System.Drawing.Size(117, 17);
             this.chkShowBoundingBox.TabIndex = 2;
@@ -139,23 +153,23 @@
             this.chkShowBoundingBox.ThreeState = true;
             this.chkShowBoundingBox.UseVisualStyleBackColor = true;
             // 
-            // chkNoOptimize
+            // chkOptimize
             // 
-            this.chkNoOptimize.AutoSize = true;
-            this.chkNoOptimize.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.chkNoOptimize.Location = new System.Drawing.Point(21, 51);
-            this.chkNoOptimize.Name = "chkNoOptimize";
-            this.chkNoOptimize.Size = new System.Drawing.Size(162, 17);
-            this.chkNoOptimize.TabIndex = 1;
-            this.chkNoOptimize.Text = "Do not try to optimize vertices";
-            this.chkNoOptimize.ThreeState = true;
-            this.chkNoOptimize.UseVisualStyleBackColor = true;
+            this.chkOptimize.AutoSize = true;
+            this.chkOptimize.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
+            this.chkOptimize.Location = new System.Drawing.Point(21, 74);
+            this.chkOptimize.Name = "chkOptimize";
+            this.chkOptimize.Size = new System.Drawing.Size(131, 17);
+            this.chkOptimize.TabIndex = 1;
+            this.chkOptimize.Text = "Try to optimize vertices";
+            this.chkOptimize.ThreeState = true;
+            this.chkOptimize.UseVisualStyleBackColor = true;
             // 
             // chkPickable
             // 
             this.chkPickable.AutoSize = true;
             this.chkPickable.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.chkPickable.Location = new System.Drawing.Point(21, 28);
+            this.chkPickable.Location = new System.Drawing.Point(21, 51);
             this.chkPickable.Name = "chkPickable";
             this.chkPickable.Size = new System.Drawing.Size(64, 17);
             this.chkPickable.TabIndex = 0;
@@ -168,7 +182,7 @@
             this.groupBox3.Controls.Add(this.grpAutoAnimate);
             this.groupBox3.Controls.Add(this.chkAutoAnimate);
             this.groupBox3.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
-            this.groupBox3.Location = new System.Drawing.Point(12, 226);
+            this.groupBox3.Location = new System.Drawing.Point(12, 237);
             this.groupBox3.Name = "groupBox3";
             this.groupBox3.Size = new System.Drawing.Size(319, 156);
             this.groupBox3.TabIndex = 4;
@@ -262,7 +276,7 @@
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
             this.CancelButton = this.butCancel;
-            this.ClientSize = new System.Drawing.Size(343, 544);
+            this.ClientSize = new System.Drawing.Size(343, 454);
             this.Controls.Add(this.groupBox3);
             this.Controls.Add(this.groupBox2);
             this.Controls.Add(this.butCancel);
@@ -295,7 +309,7 @@
         private System.Windows.Forms.Button butOK;
         private System.Windows.Forms.GroupBox groupBox2;
         private System.Windows.Forms.CheckBox chkPickable;
-        private System.Windows.Forms.CheckBox chkNoOptimize;
+        private System.Windows.Forms.CheckBox chkOptimize;
         private System.Windows.Forms.CheckBox chkShowSubMeshesBoundingBox;
         private System.Windows.Forms.CheckBox chkShowBoundingBox;
         private System.Windows.Forms.GroupBox groupBox3;
@@ -306,5 +320,6 @@
         private System.Windows.Forms.NumericUpDown nupFrom;
         private System.Windows.Forms.Label label1;
         private System.Windows.Forms.CheckBox chkAutoAnimate;
+        private System.Windows.Forms.CheckBox chkNoExport;
     }
 }

+ 6 - 4
Exporters/3ds Max/Max2Babylon/Forms/ObjectPropertiesForm.cs

@@ -16,9 +16,10 @@ namespace Max2Babylon
 
         private void butOK_Click(object sender, EventArgs e)
         {
+            Tools.UpdateCheckBox(chkNoExport, objects, "babylonjs_noexport");
             Tools.UpdateCheckBox(chkCollisions, objects, "babylonjs_checkcollisions");
-            Tools.UpdateCheckBox(chkPickable, objects, "babylonjs_checkpickable");   
-            Tools.UpdateCheckBox(chkNoOptimize, objects, "babylonjs_nooptimize");
+            Tools.UpdateCheckBox(chkPickable, objects, "babylonjs_checkpickable");
+            Tools.UpdateCheckBox(chkOptimize, objects, "babylonjs_optimizevertices");
             Tools.UpdateCheckBox(chkShowBoundingBox, objects, "babylonjs_showboundingbox");
             Tools.UpdateCheckBox(chkShowSubMeshesBoundingBox, objects, "babylonjs_showsubmeshesboundingbox");
 
@@ -34,15 +35,16 @@ namespace Max2Babylon
             {
                 var node = Loader.Core.GetSelNode(index);
 
-                if (node.ObjectRef != null && node.ObjectRef.SuperClassID == SClass_ID.Geomobject)
+                if (node.ObjectRef != null && node.ObjectRef.Eval(0).Obj.SuperClassID == SClass_ID.Geomobject)
                 {
                     objects.Add(node);
                 }
             }
 
+            Tools.PrepareCheckBox(chkNoExport, objects, "babylonjs_noexport");
             Tools.PrepareCheckBox(chkCollisions, objects, "babylonjs_checkcollisions");
             Tools.PrepareCheckBox(chkPickable, objects, "babylonjs_checkpickable");
-            Tools.PrepareCheckBox(chkNoOptimize, objects, "babylonjs_nooptimize");
+            Tools.PrepareCheckBox(chkOptimize, objects, "babylonjs_optimizevertices");
             Tools.PrepareCheckBox(chkShowBoundingBox, objects, "babylonjs_showboundingbox");
             Tools.PrepareCheckBox(chkShowSubMeshesBoundingBox, objects, "babylonjs_showsubmeshesboundingbox");
 

+ 30 - 7
Exporters/3ds Max/Max2Babylon/Max2Babylon.csproj

@@ -27,28 +27,39 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <Prefer32Bit>false</Prefer32Bit>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
     <Optimize>true</Optimize>
-    <OutputPath>bin\Release\</OutputPath>
+    <OutputPath>C:\Program Files\Autodesk\3ds Max 2013\bin\assemblies\</OutputPath>
     <DefineConstants>TRACE</DefineConstants>
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
     <Prefer32Bit>false</Prefer32Bit>
   </PropertyGroup>
   <ItemGroup>
-    <Reference Include="Autodesk.Max">
+    <Reference Include="Autodesk.Max, Version=15.6.164.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
       <HintPath>Refs\Autodesk.Max.dll</HintPath>
+      <Private>False</Private>
     </Reference>
-    <Reference Include="MaxCustomControls">
+    <Reference Include="MaxCustomControls, Version=15.6.164.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
       <HintPath>Refs\MaxCustomControls.dll</HintPath>
+      <Private>False</Private>
     </Reference>
-    <Reference Include="MaxSharp">
+    <Reference Include="MaxSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
       <HintPath>Refs\MaxSharp.dll</HintPath>
     </Reference>
-    <Reference Include="SharpDX">
-      <HintPath>Refs\SharpDX.dll</HintPath>
+    <Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="SharpDX, Version=2.4.2.0, Culture=neutral, PublicKeyToken=627a3d6d1956f55a, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\BabylonExport.Core\Refs\SharpDX.dll</HintPath>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System.Core" />
@@ -63,6 +74,13 @@
   <ItemGroup>
     <Compile Include="BabylonActionCallback.cs" />
     <Compile Include="BabylonPropertiesActionItem.cs" />
+    <Compile Include="Forms\LightPropertiesForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="Forms\LightPropertiesForm.Designer.cs">
+      <DependentUpon>LightPropertiesForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Exporter\BabylonExporter.Animation.cs" />
     <Compile Include="Exporter\BabylonExporter.ShadowGenerator.cs" />
     <Compile Include="Forms\CameraPropertiesForm.cs">
       <SubType>Form</SubType>
@@ -87,6 +105,7 @@
     <Compile Include="Exporter\BabylonExporter.Mesh.cs" />
     <Compile Include="Exporter\GlobalVertex.cs" />
     <Compile Include="GlobalUtility.cs" />
+    <Compile Include="JsonTextWriterOptimized.cs" />
     <Compile Include="Loader.cs" />
     <Compile Include="BabylonExportActionItem.cs" />
     <Compile Include="Forms\ObjectPropertiesForm.cs">
@@ -107,13 +126,14 @@
     <Compile Include="Forms\ScenePropertiesForm.Designer.cs">
       <DependentUpon>ScenePropertiesForm.cs</DependentUpon>
     </Compile>
-    <Compile Include="Tools.cs" />
+    <Compile Include="Tools\Tools.cs" />
     <Compile Include="Forms\Vector3Control.cs">
       <SubType>UserControl</SubType>
     </Compile>
     <Compile Include="Forms\Vector3Control.Designer.cs">
       <DependentUpon>Vector3Control.cs</DependentUpon>
     </Compile>
+    <Compile Include="Tools\VNormal.cs" />
   </ItemGroup>
   <ItemGroup>
     <Content Include="Refs\Autodesk.Max.dll" />
@@ -122,6 +142,9 @@
     <Content Include="Refs\MaxSharp.dll" />
   </ItemGroup>
   <ItemGroup>
+    <EmbeddedResource Include="Forms\LightPropertiesForm.resx">
+      <DependentUpon>LightPropertiesForm.cs</DependentUpon>
+    </EmbeddedResource>
     <EmbeddedResource Include="Forms\CameraPropertiesForm.resx">
       <DependentUpon>CameraPropertiesForm.cs</DependentUpon>
     </EmbeddedResource>

+ 413 - 0
Exporters/3ds Max/Max2Babylon/Tools/Tools.cs

@@ -0,0 +1,413 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+using Autodesk.Max;
+using MaxSharp;
+using SharpDX;
+using Color = MaxSharp.Color;
+
+namespace Max2Babylon
+{
+    public static class Tools
+    {
+        public const float Epsilon = 0.001f;
+
+        public static void PreparePipeline(IINode node, bool deactivate)
+        {
+            var obj = node.ObjectRef;
+
+            if (obj.SuperClassID != SClass_ID.GenDerivob)
+            {
+                return;
+            }
+
+            var derivedObject = obj as IIDerivedObject;
+
+            if (derivedObject == null)
+            {
+                return;
+            }
+
+            for (var index = 0; index < derivedObject.NumModifiers; index++)
+            {
+                var modifier = derivedObject.GetModifier(index);
+
+                if (modifier.ClassID.PartA == 9815843 && modifier.ClassID.PartB == 87654) // Skin
+                {
+                    if (deactivate)
+                    {
+                        modifier.DisableMod();
+                    }
+                    else
+                    {
+                        modifier.EnableMod();
+                    }
+                }
+            }
+        }
+
+        public static VNormal[] ComputeNormals(IMesh mesh)
+        {
+            var vnorms = new VNormal[mesh.NumVerts];
+            var fnorms = new Vector3[mesh.NumFaces];
+
+            for (var index = 0; index < mesh.NumVerts; index++)
+            {
+                vnorms[index] = new VNormal();
+            }
+
+            for (var index = 0; index < mesh.NumFaces; index++)
+            {
+                var face = mesh.Faces[index];
+                Vector3 v0 = mesh.Verts[(int)face.V[0]].ToVector3();
+                Vector3 v1 = mesh.Verts[(int)face.V[1]].ToVector3();
+                Vector3 v2 = mesh.Verts[(int)face.V[2]].ToVector3();
+
+                fnorms[index] = Vector3.Cross((v1 - v0), (v2 - v1));
+
+                for (var j = 0; j < 3; j++)
+                {
+                    vnorms[(int)face.V[j]].AddNormal(fnorms[index], face.SmGroup);
+                }
+
+                fnorms[index].Normalize();
+            }
+
+            for (var index = 0; index < mesh.NumVerts; index++)
+            {
+                vnorms[index].Normalize();
+            }
+
+            return vnorms;
+        }
+
+        public static bool IsEqualTo(this float[] value, float[] other)
+        {
+            if (value.Length != other.Length)
+            {
+                return false;
+            }
+
+            return !value.Where((t, i) => Math.Abs(t - other[i]) > Epsilon).Any();
+        }
+
+        public static float[] ToArray(this IMatrix3 value)
+        {
+            var row0 = value.GetRow(0).ToArraySwitched();
+            var row1 = value.GetRow(1).ToArraySwitched();
+            var row2 = value.GetRow(2).ToArraySwitched();
+            var row3 = value.GetRow(3).ToArraySwitched();
+
+            return new[]
+            {
+                row0[0], row0[1], row0[2], 0,
+                row2[0], row2[1], row2[2], 0,
+                row1[0], row1[1], row1[2], 0,
+                row3[0], row3[1], row3[2], 1
+            };
+        }
+
+        public static IPoint3 ToPoint3(this Vector3 value)
+        {
+            return Loader.Global.Point3.Create(value.X, value.Y, value.Z);
+        }
+
+        public static Vector3 ToVector3(this IPoint3 value)
+        {
+            return new Vector3(value.X, value.Y, value.Z);
+        }
+
+        public static Quaternion ToQuat(this IQuat value)
+        {
+            return new Quaternion(value.X, value.Z, value.Y, value.W );
+        }
+        public static float[] ToArray(this IQuat value)
+        {
+            return new[] { value.X, value.Z, value.Y, value.W };
+        }
+
+        public static float[] Scale(this Color value, float scale)
+        {
+            return new[] { value.r * scale, value.g * scale, value.b * scale };
+        }
+        public static float[] ToArray(this Color value)
+        {
+            return new[] { value.r, value.g, value.b };
+        }
+
+        public static float[] ToArray(this IPoint3 value)
+        {
+            return new[] { value.X, value.Y, value.Z };
+        }
+
+        public static float[] ToArray(this IPoint2 value)
+        {
+            return new[] { value.X, value.Y };
+        }
+
+        public static float[] ToArraySwitched(this IPoint2 value)
+        {
+            return new[] { value.X, 1.0f - value.Y };
+        }
+
+        public static float[] ToArraySwitched(this IPoint3 value)
+        {
+            return new[] { value.X, value.Z, value.Y };
+        }
+
+        public static float[] ToArray(this IColor value)
+        {
+            return new[] { value.R, value.G, value.B };
+        }
+
+        public static IEnumerable<Node> NodesListBySuperClass(this Scene scene, SuperClassID sid)
+        {
+            return from n in scene.NodeTree where n.Object != null && n._Node.EvalWorldState(0, false).Obj.SuperClassID == sid select n;
+        }
+
+        public static IEnumerable<Node> NodesListBySuperClasses(this Scene scene, SuperClassID[] sids)
+        {
+            return from n in scene.NodeTree where n.Object != null && sids.Any(sid => n._Node.EvalWorldState(0, false).Obj.SuperClassID == sid) select n;
+        }
+
+        public static float ConvertFov(float fov)
+        {
+            return (float)(2.0f * Math.Atan(Math.Tan(fov / 2.0f) / Loader.Core.ImageAspRatio));
+        }
+
+        public static bool HasParent(this Node node)
+        {
+            return node.Parent != null && node.Parent.Object != null;
+        }
+
+        public static Guid GetGuid(this Animatable node)
+        {
+            var appData = node.GetAppData(new ClassID(Loader.Class_ID), SuperClassID.BaseNode);
+
+            var uidData = appData.GetChunk(0);
+            Guid uid;
+
+            if (uidData != null)
+            {
+                uid = new Guid(uidData);
+            }
+            else
+            {
+                uid = Guid.NewGuid();
+                appData.AddChunk(0, uid.ToByteArray());
+            }
+
+            return uid;
+        }
+
+        public static Guid GetGuid(this IINode node)
+        {
+            return GetGuid(Animatable.CreateWrapper<Node>(node));
+        }
+
+        public static string GetLocalData(this Node node)
+        {
+            var appData = node.GetAppData(new ClassID(Loader.Class_ID), SuperClassID.BaseNode);
+
+            var uidData = appData.GetChunk(1);
+
+            if (uidData != null)
+            {
+                return System.Text.Encoding.UTF8.GetString(uidData);
+            }
+
+            return "";
+        }
+
+        public static void SetLocalData(this Node node, string value)
+        {
+            var appData = node.GetAppData(new ClassID(Loader.Class_ID), SuperClassID.BaseNode);
+
+            var uidData = appData.GetChunk(1);
+
+            if (uidData != null)
+            {
+                appData.RemoveChunk(1);
+            }
+
+            appData.AddChunk(1, System.Text.Encoding.UTF8.GetBytes(value));
+        }
+
+        public static IMatrix3 GetWorldMatrix(this Node node, TimeValue t, bool parent)
+        {
+            var innerNode = node._Node;
+
+            var tm = innerNode.GetNodeTM(t, Interval.Forever._IInterval);
+            var ptm = innerNode.ParentNode.GetNodeTM(t, Interval.Forever._IInterval);
+
+            if (!parent)
+                return tm;
+
+            if (innerNode.ParentNode.SuperClassID == SuperClassID.Camera)
+            {
+                var r = ptm.GetRow(3);
+                ptm.IdentityMatrix();
+                ptm.SetRow(3, r);
+            }
+
+            ptm.Invert();
+            return tm.Multiply(ptm);
+        }
+
+        public static ITriObject GetMesh(this IObject obj, out bool mustBeDeleted)
+        {
+            mustBeDeleted = false;
+            if (obj.CanConvertToType(ClassID.TriObject._IClass_ID) == 0)
+                return null;
+
+            var tri = obj.ConvertToType(0, ClassID.TriObject._IClass_ID) as ITriObject;
+
+            mustBeDeleted = (tri != obj);
+
+            return tri;
+        }
+
+        public static bool IsAlmostEqualTo(this IPoint3 current, IPoint3 other, float epsilon)
+        {
+            if (Math.Abs(current.X - other.X) > epsilon)
+            {
+                return false;
+            }
+
+            if (Math.Abs(current.Y - other.Y) > epsilon)
+            {
+                return false;
+            }
+
+            if (Math.Abs(current.Z - other.Z) > epsilon)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        public static bool IsAlmostEqualTo(this IPoint2 current, IPoint2 other, float epsilon)
+        {
+            if (Math.Abs(current.X - other.X) > epsilon)
+            {
+                return false;
+            }
+
+            if (Math.Abs(current.Y - other.Y) > epsilon)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        public static bool GetBoolProperty(this IINode node, string propertyName, int defaultState = 0)
+        {
+            int state = defaultState;
+            node.GetUserPropBool(ref propertyName, ref state);
+
+            return state == 1;
+        }
+
+        public static float GetFloatProperty(this IINode node, string propertyName, float defaultState = 0)
+        {
+            float state = defaultState;
+            node.GetUserPropFloat(ref propertyName, ref state);
+
+            return state;
+        }
+
+        public static float[] GetVector3Property(this IINode node, string propertyName)
+        {
+            float state0 = 0;
+            string name = propertyName + "_x";
+            node.GetUserPropFloat(ref name, ref state0);
+
+            float state1 = 0;
+            name = propertyName + "_y";
+            node.GetUserPropFloat(ref name, ref state1);
+
+            float state2 = 0;
+            name = propertyName + "_z";
+            node.GetUserPropFloat(ref name, ref state2);
+
+            return new[] { state0, state1, state2 };
+        }
+
+        public static void PrepareCheckBox(CheckBox checkBox, List<IINode> nodes, string propertyName, int defaultState = 0)
+        {
+            checkBox.CheckState = CheckState.Indeterminate;
+            foreach (var node in nodes)
+            {
+                var state = node.GetBoolProperty(propertyName, defaultState);
+
+                if (checkBox.CheckState == CheckState.Indeterminate)
+                {
+                    checkBox.CheckState = state ? CheckState.Checked : CheckState.Unchecked;
+                }
+                else
+                {
+                    if (!state && checkBox.CheckState == CheckState.Checked ||
+                        state && checkBox.CheckState == CheckState.Unchecked)
+                    {
+                        checkBox.CheckState = CheckState.Indeterminate;
+                        break;
+                    }
+                }
+            }
+        }
+
+        public static void UpdateCheckBox(CheckBox checkBox, List<IINode> nodes, string propertyName)
+        {
+            foreach (var node in nodes)
+            {
+                if (checkBox.CheckState != CheckState.Indeterminate)
+                {
+                    node.SetUserPropBool(ref propertyName, checkBox.CheckState == CheckState.Checked);
+                }
+            }
+        }
+
+        public static void PrepareNumericUpDown(NumericUpDown nup, List<IINode> nodes, string propertyName, float defaultState = 0)
+        {
+            nup.Value = (decimal)nodes[0].GetFloatProperty(propertyName, defaultState);
+        }
+
+        public static void UpdateNumericUpDown(NumericUpDown nup, List<IINode> nodes, string propertyName)
+        {
+            foreach (var node in nodes)
+            {
+                node.SetUserPropFloat(ref propertyName, (float)nup.Value);
+            }
+        }
+
+        public static void PrepareVector3Control(Vector3Control vector3Control, IINode node, string propertyName, float defaultX = 0, float defaultY = 0, float defaultZ = 0)
+        {
+            vector3Control.X = node.GetFloatProperty(propertyName + "_x", defaultX);
+            vector3Control.Y = node.GetFloatProperty(propertyName + "_y", defaultY);
+            vector3Control.Z = node.GetFloatProperty(propertyName + "_z", defaultZ);
+        }
+
+        public static void UpdateVector3Control(Vector3Control vector3Control, IINode node, string propertyName)
+        {
+            string name = propertyName + "_x";
+            node.SetUserPropFloat(ref name, vector3Control.X);
+
+            name = propertyName + "_y";
+            node.SetUserPropFloat(ref name, vector3Control.Y);
+
+            name = propertyName + "_z";
+            node.SetUserPropFloat(ref name, vector3Control.Z);
+        }
+
+        public static void UpdateVector3Control(Vector3Control vector3Control, List<IINode> nodes, string propertyName)
+        {
+            foreach (var node in nodes)
+            {
+                UpdateVector3Control(vector3Control, node, propertyName);
+            }
+        }
+    }
+}

+ 84 - 0
Exporters/3ds Max/Max2Babylon/Tools/VNormal.cs

@@ -0,0 +1,84 @@
+using Autodesk.Max;
+using SharpDX;
+
+namespace Max2Babylon
+{
+    public class VNormal
+    {
+        Vector3 norm;
+        uint smooth;
+        VNormal next;
+        bool init;
+
+        public VNormal()
+        {
+            smooth = 0;
+            next = null;
+            init = false;
+            norm = new Vector3(0, 0, 0);
+        }
+
+        public VNormal(Vector3 n, uint s)
+        {
+            next = null;
+            init = true;
+            norm = n;
+            smooth = s;
+        }
+
+        public void AddNormal(Vector3 n, uint s)
+        {
+            if (((s & smooth) == 0) && init)
+            {
+                if (next != null)
+                    next.AddNormal(n, s);
+                else
+                {
+                    next = new VNormal(n, s);
+                }
+            }
+            else
+            {
+                norm += n;
+                smooth |= s;
+                init = true;
+            }
+        }
+
+        public IPoint3 GetNormal(uint s)
+        {
+            if (((smooth & s) != 0) || next == null)
+                return norm.ToPoint3();
+
+            return next.GetNormal(s);
+        }
+
+        // Normalize each normal in the list
+        public void Normalize()
+        {
+            VNormal ptr = next;
+            VNormal prev = this;
+
+            while (ptr != null)
+            {
+                if ((ptr.smooth & smooth) != 0)
+                {
+                    norm += ptr.norm;
+                    prev.next = ptr.next;
+                    ptr = prev.next;
+                }
+                else
+                {
+                    prev = ptr;
+                    ptr = ptr.next;
+                }
+            }
+            norm.Normalize();
+
+            if (next != null)
+            {
+                next.Normalize();
+            }
+        }
+    }
+}

+ 0 - 4
Exporters/3ds Max/Max2Babylon/packages.config

@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
-  <package id="Newtonsoft.Json" version="6.0.3" targetFramework="net45" />
-</packages>

+ 1 - 0
Exporters/3ds Max/readme.md

@@ -34,6 +34,7 @@ This exporter is designed for 3ds Max 2013+. You just have to unzip the content
  - Collisions (*)
  - Pickable (*)
  - Position / rotation / scaling
+ - Smoothing groups
  - Geometry (position, normal, texture coordinates (2 channels))
  - Show Bounding box and submeshes bounding boxes (*)
  - Animations: Position, scaling, rotation, visibility

File diff suppressed because it is too large
+ 9 - 9
babylon.1.12-beta.js