Procházet zdrojové kódy

Merge pull request #2944 from noalak/master

glTF Exporter > skins + samples
David Catuhe před 7 roky
rodič
revize
c8889e5564
95 změnil soubory, kde provedl 1138 přidání a 175 odebrání
  1. 1 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonExport.Entities.csproj
  2. 313 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonMatrix.cs
  3. 103 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonQuaternion.cs
  4. 22 0
      Exporters/3ds Max/BabylonExport.Entities/BabylonVector3.cs
  5. 11 0
      Exporters/3ds Max/GltfExport.Entities/GLTF.cs
  6. 1 0
      Exporters/3ds Max/GltfExport.Entities/GLTFExport.Entities.csproj
  7. 4 0
      Exporters/3ds Max/GltfExport.Entities/GLTFMesh.cs
  8. 3 0
      Exporters/3ds Max/GltfExport.Entities/GLTFNode.cs
  9. 27 0
      Exporters/3ds Max/GltfExport.Entities/GLTFSkin.cs
  10. binární
      Exporters/3ds Max/Max2Babylon-0.18.0.zip
  11. 3 0
      Exporters/3ds Max/Max2Babylon/2015/Max2Babylon2015.csproj
  12. 3 0
      Exporters/3ds Max/Max2Babylon/2017/Max2Babylon2017.csproj
  13. 23 7
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Animation.cs
  14. 11 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.AbstractMesh.cs
  15. 166 54
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Animation.cs
  16. 1 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Camera.cs
  17. 1 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Light.cs
  18. 34 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Material.cs
  19. 65 41
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Mesh.cs
  20. 202 0
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Skin.cs
  21. 6 6
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.cs
  22. 29 23
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Mesh.cs
  23. 9 38
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.Skeleton.cs
  24. 4 4
      Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.cs
  25. 21 0
      Exporters/3ds Max/Max2Babylon/Exporter/GLTFBufferService.cs
  26. 2 0
      Exporters/3ds Max/Max2Babylon/Exporter/GLTFGlobalVertex.cs
  27. 2 2
      Exporters/3ds Max/Max2Babylon/Forms/ScenePropertiesForm.cs
  28. 51 0
      Exporters/3ds Max/Max2Babylon/Tools/Tools.cs
  29. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/2CylinderEngine/2CylinderEngine.babylon
  30. binární
      Exporters/3ds Max/Samples/glTF 2.0/2CylinderEngine/2CylinderEngine.bin
  31. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/2CylinderEngine/2CylinderEngine.gltf
  32. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/AnimatedMorphCube/AnimatedMorphCube.babylon
  33. binární
      Exporters/3ds Max/Samples/glTF 2.0/AnimatedMorphCube/AnimatedMorphCube.bin
  34. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/AnimatedMorphCube/AnimatedMorphCube.gltf
  35. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/Buggy/Buggy.babylon
  36. binární
      Exporters/3ds Max/Samples/glTF 2.0/Buggy/Buggy.bin
  37. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/Buggy/Buggy.gltf
  38. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/CartoonHouses.babylon
  39. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/CartoonHouses.bin
  40. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/CartoonHouses.gltf
  41. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/arbre.jpg
  42. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/arbre_baseColor.jpg
  43. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/bois_baseColor.jpg
  44. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/cardboard_04_from_radu_luchian_dot_com.jpg
  45. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/cloture.jpg
  46. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/cloture_baseColor.jpg
  47. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/entree.jpg
  48. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/entree_baseColor.jpg
  49. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison.jpg
  50. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison2.jpg
  51. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison2_baseColor.jpg
  52. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison3.jpg
  53. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison3_baseColor.jpg
  54. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison_baseColor.jpg
  55. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/sol.jpg
  56. binární
      Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/sol_baseColor.jpg
  57. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/Instances/Instances.babylon
  58. binární
      Exporters/3ds Max/Samples/glTF 2.0/Instances/Instances.bin
  59. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/Instances/Instances.gltf
  60. binární
      Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/Material #37_baseColor.jpg
  61. binární
      Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/Material #40_metallicRoughness.jpg
  62. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/PBR_Textures.babylon
  63. binární
      Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/PBR_Textures.bin
  64. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/PBR_Textures.gltf
  65. binární
      Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/bump.jpg
  66. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/RiggedElf/RiggedElf.babylon
  67. binární
      Exporters/3ds Max/Samples/glTF 2.0/RiggedElf/RiggedElf.bin
  68. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/RiggedElf/RiggedElf.gltf
  69. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/SimpleTriangle/SimpleTriangle.babylon
  70. binární
      Exporters/3ds Max/Samples/glTF 2.0/SimpleTriangle/SimpleTriangle.bin
  71. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/SimpleTriangle/SimpleTriangle.gltf
  72. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace.babylon
  73. binární
      Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace.bin
  74. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace.gltf
  75. binární
      Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace_texture_0001.jpg
  76. binární
      Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace_texture_0002.jpg
  77. binární
      Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/m0smiling_face_DF_baseColor.jpg
  78. binární
      Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/m0smiling_face_DF_metallicRoughness.jpg
  79. binární
      Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/Material #39_baseColor.jpg
  80. binární
      Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/Material #39_metallicRoughness.jpg
  81. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle.babylon
  82. binární
      Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle.bin
  83. 1 0
      Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle.gltf
  84. binární
      Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle_Emissive.png
  85. binární
      Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle_Normal.png
  86. binární
      Exporters/3ds Max/Samples/sourceModels/2CylinderEngine/2CylinderEngine.FBX
  87. binární
      Exporters/3ds Max/Samples/sourceModels/AnimatedMorphCube/AnimatedMorphCube.FBX
  88. binární
      Exporters/3ds Max/Samples/sourceModels/Buggy/Buggy.FBX
  89. binární
      Exporters/3ds Max/Samples/sourceModels/CartoonHouses/CartoonHouses.FBX
  90. binární
      Exporters/3ds Max/Samples/sourceModels/Instances/Instances.FBX
  91. binární
      Exporters/3ds Max/Samples/sourceModels/PBR_Textures_MetallicRoughness/PBR_Textures.FBX
  92. binární
      Exporters/3ds Max/Samples/sourceModels/RiggedElf/RiggedElf.FBX
  93. binární
      Exporters/3ds Max/Samples/sourceModels/SimpleTriangle/SimpleTriangle.FBX
  94. binární
      Exporters/3ds Max/Samples/sourceModels/SmilingFace/SmilingFace.FBX
  95. binární
      Exporters/3ds Max/Samples/sourceModels/WaterBottle/WaterBottle.FBX

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

@@ -72,6 +72,7 @@
     <Compile Include="BabylonMaterial.cs" />
     <Compile Include="BabylonNode.cs" />
     <Compile Include="BabylonPBRMaterial.cs" />
+    <Compile Include="BabylonMatrix.cs" />
     <Compile Include="BabylonShaderMaterial.cs" />
     <Compile Include="BabylonColor3.cs" />
     <Compile Include="BabylonStandardMaterial.cs" />

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

@@ -0,0 +1,313 @@
+using System;
+
+namespace BabylonExport.Entities
+{
+    public class BabylonMatrix
+    {
+        public float[] m = new float[16];
+
+        /**
+         * Returns a new Matrix as the passed inverted one.  
+         */
+        public static BabylonMatrix Invert(BabylonMatrix source) {
+            var result = new BabylonMatrix();
+            source.invertToRef(result);
+            return result;
+        }
+
+        /**
+         * Inverts in place the Matrix.  
+         * Returns the Matrix inverted.  
+         */
+        public BabylonMatrix invert() {
+            this.invertToRef(this);
+            return this;
+        }
+
+        /**
+         * Sets the passed matrix with the current inverted Matrix.  
+         * Returns the unmodified current Matrix.  
+         */
+        public BabylonMatrix invertToRef(BabylonMatrix other) {
+            var l1 = this.m[0];
+            var l2 = this.m[1];
+            var l3 = this.m[2];
+            var l4 = this.m[3];
+            var l5 = this.m[4];
+            var l6 = this.m[5];
+            var l7 = this.m[6];
+            var l8 = this.m[7];
+            var l9 = this.m[8];
+            var l10 = this.m[9];
+            var l11 = this.m[10];
+            var l12 = this.m[11];
+            var l13 = this.m[12];
+            var l14 = this.m[13];
+            var l15 = this.m[14];
+            var l16 = this.m[15];
+            var l17 = (l11 * l16) - (l12 * l15);
+            var l18 = (l10 * l16) - (l12 * l14);
+            var l19 = (l10 * l15) - (l11 * l14);
+            var l20 = (l9 * l16) - (l12 * l13);
+            var l21 = (l9 * l15) - (l11 * l13);
+            var l22 = (l9 * l14) - (l10 * l13);
+            var l23 = ((l6 * l17) - (l7 * l18)) + (l8 * l19);
+            var l24 = -(((l5 * l17) - (l7 * l20)) + (l8 * l21));
+            var l25 = ((l5 * l18) - (l6 * l20)) + (l8 * l22);
+            var l26 = -(((l5 * l19) - (l6 * l21)) + (l7 * l22));
+            var l27 = 1.0f / ((((l1 * l23) + (l2 * l24)) + (l3 * l25)) + (l4 * l26));
+            var l28 = (l7 * l16) - (l8 * l15);
+            var l29 = (l6 * l16) - (l8 * l14);
+            var l30 = (l6 * l15) - (l7 * l14);
+            var l31 = (l5 * l16) - (l8 * l13);
+            var l32 = (l5 * l15) - (l7 * l13);
+            var l33 = (l5 * l14) - (l6 * l13);
+            var l34 = (l7 * l12) - (l8 * l11);
+            var l35 = (l6 * l12) - (l8 * l10);
+            var l36 = (l6 * l11) - (l7 * l10);
+            var l37 = (l5 * l12) - (l8 * l9);
+            var l38 = (l5 * l11) - (l7 * l9);
+            var l39 = (l5 * l10) - (l6 * l9);
+
+            other.m[0] = l23* l27;
+            other.m[4] = l24* l27;
+            other.m[8] = l25* l27;
+            other.m[12] = l26* l27;
+            other.m[1] = -(((l2* l17) - (l3* l18)) + (l4* l19)) * l27;
+            other.m[5] = (((l1* l17) - (l3* l20)) + (l4* l21)) * l27;
+            other.m[9] = -(((l1* l18) - (l2* l20)) + (l4* l22)) * l27;
+            other.m[13] = (((l1* l19) - (l2* l21)) + (l3* l22)) * l27;
+            other.m[2] = (((l2* l28) - (l3* l29)) + (l4* l30)) * l27;
+            other.m[6] = -(((l1* l28) - (l3* l31)) + (l4* l32)) * l27;
+            other.m[10] = (((l1* l29) - (l2* l31)) + (l4* l33)) * l27;
+            other.m[14] = -(((l1* l30) - (l2* l32)) + (l3* l33)) * l27;
+            other.m[3] = -(((l2* l34) - (l3* l35)) + (l4* l36)) * l27;
+            other.m[7] = (((l1* l34) - (l3* l37)) + (l4* l38)) * l27;
+            other.m[11] = -(((l1* l35) - (l2* l37)) + (l4* l39)) * l27;
+            other.m[15] = (((l1* l36) - (l2* l38)) + (l3* l39)) * l27;
+            
+            return this;
+        }
+
+        /**
+         * Returns a new Matrix composed by the passed scale (vector3), rotation (quaternion) and translation (vector3).  
+         */
+        public static BabylonMatrix Compose(BabylonVector3 scale, BabylonQuaternion rotation, BabylonVector3 translation)
+        {
+            var result = BabylonMatrix.Identity();
+            BabylonMatrix.ComposeToRef(scale, rotation, translation, result);
+            return result;
+        }
+
+        /**
+         * Update a Matrix with values composed by the passed scale (vector3), rotation (quaternion) and translation (vector3).  
+         */
+        public static void ComposeToRef(BabylonVector3 scale, BabylonQuaternion rotation, BabylonVector3 translation, BabylonMatrix result) {
+            var matrix1 = new BabylonMatrix();
+            BabylonMatrix.FromValuesToRef(scale.X, 0, 0, 0,
+                0, scale.Y, 0, 0,
+                0, 0, scale.Z, 0,
+                0, 0, 0, 1, matrix1);
+
+            var matrix0 = new BabylonMatrix();
+            rotation.toRotationMatrix(matrix0);
+            matrix1.multiplyToRef(matrix0, result);
+
+            result.setTranslation(translation);
+        }
+        
+        /**
+         * Returns a new indentity Matrix.  
+         */
+        public static BabylonMatrix Identity()
+        {
+            var matrix = new BabylonMatrix();
+             BabylonMatrix.FromValuesToRef(
+                1.0f, 0.0f, 0.0f, 0.0f,
+                0.0f, 1.0f, 0.0f, 0.0f,
+                0.0f, 0.0f, 1.0f, 0.0f,
+                0.0f, 0.0f, 0.0f, 1.0f, matrix);
+            return matrix;
+        }
+
+        public static BabylonMatrix operator *(BabylonMatrix a, BabylonMatrix b)
+        {
+            return a.multiply(b);
+        }
+
+        /**
+         * Returns a new Matrix set with the multiplication result of the current Matrix and the passed one.  
+         */
+        public BabylonMatrix multiply(BabylonMatrix other) {
+            var result = new BabylonMatrix();
+            this.multiplyToRef(other, result);
+            return result;
+        }
+
+        /**
+         * Sets the passed matrix "result" with the multiplication result of the current Matrix and the passed one.  
+         */
+        public BabylonMatrix multiplyToRef(BabylonMatrix other, BabylonMatrix result) {
+            this.multiplyToArray(other, result.m, 0);
+            return this;
+        }
+
+        /**
+         * Sets the Float32Array "result" from the passed index "offset" with the multiplication result of the current Matrix and the passed one.  
+         */
+        public BabylonMatrix multiplyToArray(BabylonMatrix other, float[] result, int offset)
+        {
+            var tm0 = this.m[0];
+            var tm1 = this.m[1];
+            var tm2 = this.m[2];
+            var tm3 = this.m[3];
+            var tm4 = this.m[4];
+            var tm5 = this.m[5];
+            var tm6 = this.m[6];
+            var tm7 = this.m[7];
+            var tm8 = this.m[8];
+            var tm9 = this.m[9];
+            var tm10 = this.m[10];
+            var tm11 = this.m[11];
+            var tm12 = this.m[12];
+            var tm13 = this.m[13];
+            var tm14 = this.m[14];
+            var tm15 = this.m[15];
+
+            var om0 = other.m[0];
+            var om1 = other.m[1];
+            var om2 = other.m[2];
+            var om3 = other.m[3];
+            var om4 = other.m[4];
+            var om5 = other.m[5];
+            var om6 = other.m[6];
+            var om7 = other.m[7];
+            var om8 = other.m[8];
+            var om9 = other.m[9];
+            var om10 = other.m[10];
+            var om11 = other.m[11];
+            var om12 = other.m[12];
+            var om13 = other.m[13];
+            var om14 = other.m[14];
+            var om15 = other.m[15];
+
+            result[offset] = tm0* om0 + tm1* om4 + tm2* om8 + tm3* om12;
+            result[offset + 1] = tm0* om1 + tm1* om5 + tm2* om9 + tm3* om13;
+            result[offset + 2] = tm0* om2 + tm1* om6 + tm2* om10 + tm3* om14;
+            result[offset + 3] = tm0* om3 + tm1* om7 + tm2* om11 + tm3* om15;
+
+            result[offset + 4] = tm4* om0 + tm5* om4 + tm6* om8 + tm7* om12;
+            result[offset + 5] = tm4* om1 + tm5* om5 + tm6* om9 + tm7* om13;
+            result[offset + 6] = tm4* om2 + tm5* om6 + tm6* om10 + tm7* om14;
+            result[offset + 7] = tm4* om3 + tm5* om7 + tm6* om11 + tm7* om15;
+
+            result[offset + 8] = tm8* om0 + tm9* om4 + tm10* om8 + tm11* om12;
+            result[offset + 9] = tm8* om1 + tm9* om5 + tm10* om9 + tm11* om13;
+            result[offset + 10] = tm8* om2 + tm9* om6 + tm10* om10 + tm11* om14;
+            result[offset + 11] = tm8* om3 + tm9* om7 + tm10* om11 + tm11* om15;
+
+            result[offset + 12] = tm12* om0 + tm13* om4 + tm14* om8 + tm15* om12;
+            result[offset + 13] = tm12* om1 + tm13* om5 + tm14* om9 + tm15* om13;
+            result[offset + 14] = tm12* om2 + tm13* om6 + tm14* om10 + tm15* om14;
+            result[offset + 15] = tm12* om3 + tm13* om7 + tm14* om11 + tm15* om15;
+            return this;
+        }
+
+        /**
+         * Inserts the translation vector in the current Matrix.  
+         * Returns the updated Matrix.  
+         */
+        public BabylonMatrix setTranslation(BabylonVector3 vector3) {
+            this.m[12] = vector3.X;
+            this.m[13] = vector3.Y;
+            this.m[14] = vector3.Z;
+            
+            return this;
+        }
+
+        /**
+         * Decomposes the current Matrix into : 
+         * - a scale vector3 passed as a reference to update, 
+         * - a rotation quaternion passed as a reference to update,
+         * - a translation vector3 passed as a reference to update.  
+         * Returns the boolean `true`.  
+         */
+        public bool decompose(BabylonVector3 scale, BabylonQuaternion rotation, BabylonVector3 translation)
+        {
+            translation.X = this.m[12];
+            translation.Y = this.m[13];
+            translation.Z = this.m[14];
+
+            scale.X = (float)Math.Sqrt(this.m[0] * this.m[0] + this.m[1] * this.m[1] + this.m[2] * this.m[2]);
+            scale.Y = (float)Math.Sqrt(this.m[4] * this.m[4] + this.m[5] * this.m[5] + this.m[6] * this.m[6]);
+            scale.Z = (float)Math.Sqrt(this.m[8] * this.m[8] + this.m[9] * this.m[9] + this.m[10] * this.m[10]);
+
+            if (this.determinant() <= 0) {
+                scale.Y *= -1;
+            }
+
+            if (scale.X == 0 || scale.Y == 0 || scale.Z == 0) {
+                rotation.X = 0;
+                rotation.Y = 0;
+                rotation.Z = 0;
+                rotation.W = 1;
+                return false;
+            }
+
+            var matrix = new BabylonMatrix();
+
+            BabylonMatrix.FromValuesToRef(
+                this.m[0] / scale.X, this.m[1] / scale.X, this.m[2] / scale.X, 0,
+                this.m[4] / scale.Y, this.m[5] / scale.Y, this.m[6] / scale.Y, 0,
+                this.m[8] / scale.Z, this.m[9] / scale.Z, this.m[10] / scale.Z, 0,
+                0, 0, 0, 1, matrix);
+
+            BabylonQuaternion.FromRotationMatrixToRef(matrix, rotation);
+
+            return true;
+        }
+
+        /**
+         * Returns the matrix determinant (float).  
+         */
+        public float determinant()
+        {
+            var temp1 = (this.m[10] * this.m[15]) - (this.m[11] * this.m[14]);
+            var temp2 = (this.m[9] * this.m[15]) - (this.m[11] * this.m[13]);
+            var temp3 = (this.m[9] * this.m[14]) - (this.m[10] * this.m[13]);
+            var temp4 = (this.m[8] * this.m[15]) - (this.m[11] * this.m[12]);
+            var temp5 = (this.m[8] * this.m[14]) - (this.m[10] * this.m[12]);
+            var temp6 = (this.m[8] * this.m[13]) - (this.m[9] * this.m[12]);
+
+            return ((((this.m[0] * (((this.m[5] * temp1) - (this.m[6] * temp2)) + (this.m[7] * temp3))) - (this.m[1] * (((this.m[4] * temp1) -
+                (this.m[6] * temp4)) + (this.m[7] * temp5)))) + (this.m[2] * (((this.m[4] * temp2) - (this.m[5] * temp4)) + (this.m[7] * temp6)))) -
+                (this.m[3] * (((this.m[4] * temp3) - (this.m[5] * temp5)) + (this.m[6] * temp6))));
+        }
+
+        /**
+            * Sets the passed matrix "result" with the 16 passed floats.  
+            */
+        public static void FromValuesToRef(float initialM11, float initialM12, float initialM13, float initialM14,
+        float initialM21, float initialM22, float initialM23, float initialM24,
+        float initialM31, float initialM32, float initialM33, float initialM34,
+        float initialM41, float initialM42, float initialM43, float initialM44, BabylonMatrix result)
+        {
+            result.m[0] = initialM11;
+            result.m[1] = initialM12;
+            result.m[2] = initialM13;
+            result.m[3] = initialM14;
+            result.m[4] = initialM21;
+            result.m[5] = initialM22;
+            result.m[6] = initialM23;
+            result.m[7] = initialM24;
+            result.m[8] = initialM31;
+            result.m[9] = initialM32;
+            result.m[10] = initialM33;
+            result.m[11] = initialM34;
+            result.m[12] = initialM41;
+            result.m[13] = initialM42;
+            result.m[14] = initialM43;
+            result.m[15] = initialM44;
+        }
+}
+}

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

@@ -9,6 +9,20 @@ namespace BabylonExport.Entities
         public float Z { get; set; }
         public float W { get; set; }
 
+
+        public BabylonQuaternion() { }
+
+        /**
+         * Creates a new Quaternion from the passed floats.  
+         */
+        public BabylonQuaternion(float x, float y, float z, float w)
+        {
+            this.X = x;
+            this.Y = y;
+            this.Z = z;
+            this.W = w;
+        }
+
         public float[] ToArray()
         {
             return new [] {X, Y, Z, W};
@@ -55,5 +69,94 @@ namespace BabylonExport.Entities
         {
             return "{ X=" + X + ", Y=" + Y + ", Z=" + Z + ", W=" + W + " }";
         }
+
+        /**
+         * Updates the passed quaternion "result" with the passed rotation matrix values.  
+         */
+        public static void FromRotationMatrixToRef(BabylonMatrix matrix, BabylonQuaternion result) {
+            var data = matrix.m;
+            float m11 = data[0], m12 = data[4], m13 = data[8];
+            float m21 = data[1], m22 = data[5], m23 = data[9];
+            float m31 = data[2], m32 = data[6], m33 = data[10];
+            var trace = m11 + m22 + m33;
+            float s;
+
+            if (trace > 0) {
+
+                s = (float) (0.5 / Math.Sqrt(trace + 1.0));
+
+                result.W = 0.25f / s;
+                result.X = (m32 - m23) * s;
+                result.Y = (m13 - m31) * s;
+                result.Z = (m21 - m12) * s;
+            } else if (m11 > m22 && m11 > m33) {
+
+                s = (float)(2.0 * Math.Sqrt(1.0 + m11 - m22 - m33));
+
+                result.W = (m32 - m23) / s;
+                result.X = 0.25f * s;
+                result.Y = (m12 + m21) / s;
+                result.Z = (m13 + m31) / s;
+            } else if (m22 > m33) {
+
+                s = (float)(2.0 * Math.Sqrt(1.0 + m22 - m11 - m33));
+
+                result.W = (m13 - m31) / s;
+                result.X = (m12 + m21) / s;
+                result.Y = 0.25f * s;
+                result.Z = (m23 + m32) / s;
+            } else {
+
+                s = (float)(2.0 * Math.Sqrt(1.0 + m33 - m11 - m22));
+
+                result.W = (m21 - m12) / s;
+                result.X = (m13 + m31) / s;
+                result.Y = (m23 + m32) / s;
+                result.Z = 0.25f * s;
+            }
+        }
+
+        /**
+         * Updates the passed rotation matrix with the current Quaternion values.  
+         * Returns the current Quaternion.  
+         */
+        public BabylonQuaternion toRotationMatrix(BabylonMatrix result) {
+            var xx = this.X * this.X;
+            var yy = this.Y * this.Y;
+            var zz = this.Z * this.Z;
+            var xy = this.X * this.Y;
+            var zw = this.Z * this.W;
+            var zx = this.Z * this.X;
+            var yw = this.Y * this.W;
+            var yz = this.Y * this.Z;
+            var xw = this.X * this.W;
+
+            result.m[0] = 1.0f - (2.0f * (yy + zz));
+            result.m[1] = 2.0f * (xy + zw);
+            result.m[2] = 2.0f * (zx - yw);
+            result.m[3] = 0;
+            result.m[4] = 2.0f * (xy - zw);
+            result.m[5] = 1.0f - (2.0f * (zz + xx));
+            result.m[6] = 2.0f * (yz + xw);
+            result.m[7] = 0;
+            result.m[8] = 2.0f * (zx + yw);
+            result.m[9] = 2.0f * (yz - xw);
+            result.m[10] = 1.0f - (2.0f * (yy + xx));
+            result.m[11] = 0;
+            result.m[12] = 0;
+            result.m[13] = 0;
+            result.m[14] = 0;
+            result.m[15] = 1.0f;
+            
+            return this;
+        }
+
+        /**
+         * Retuns a new Quaternion set from the starting index of the passed array.
+         */
+        public static BabylonQuaternion FromArray(float[] array, int offset = 0)
+        {
+            return new BabylonQuaternion(array[offset], array[offset + 1], array[offset + 2], array[offset + 3]);
+        }
     }
 }

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

@@ -8,6 +8,20 @@ namespace BabylonExport.Entities
         public float Y { get; set; }
         public float Z { get; set; }
 
+        public BabylonVector3() { }
+
+        /**
+         * Creates a new Vector3 object from the passed x, y, z (floats) coordinates.  
+         * A Vector3 is the main object used in 3D geometry.  
+         * It can represent etiher the coordinates of a point the space, either a direction.  
+         */
+        public BabylonVector3(float x, float y, float z)
+        {
+            this.X = x;
+            this.Y = y;
+            this.Z = z;
+        }
+
         public float[] ToArray()
         {
             return new [] {X, Y, Z};
@@ -69,6 +83,14 @@ namespace BabylonExport.Entities
             return result;
         }
 
+        /**
+         * Returns a new Vector3 set from the index "offset" of the passed array.
+         */
+        public static BabylonVector3 FromArray(float[] array, int offset = 0)
+        {
+            return new BabylonVector3(array[offset], array[offset + 1], array[offset + 2]);
+        }
+
         public override string ToString()
         {
             return "{ X=" + X + ", Y=" + Y + ", Z=" + Z + " }";

+ 11 - 0
Exporters/3ds Max/GltfExport.Entities/GLTF.cs

@@ -49,6 +49,9 @@ namespace GLTFExport.Entities
         [DataMember(EmitDefaultValue = false)]
         public GLTFAnimation[] animations { get; set; }
 
+        [DataMember(EmitDefaultValue = false)]
+        public GLTFSkin[] skins { get; set; }
+
         public string OutputFolder { get; private set; }
         public string OutputFile { get; private set; }
 
@@ -63,11 +66,14 @@ namespace GLTFExport.Entities
         public List<GLTFImage> ImagesList { get; private set; }
         public List<GLTFSampler> SamplersList { get; private set; }
         public List<GLTFAnimation> AnimationsList { get; private set; }
+        public List<GLTFSkin> SkinsList { get; private set; }
 
         public GLTFBuffer buffer;
         public GLTFBufferView bufferViewScalar;
         public GLTFBufferView bufferViewFloatVec3;
         public GLTFBufferView bufferViewFloatVec4;
+        public GLTFBufferView bufferViewFloatMat4;
+        public GLTFBufferView bufferViewUnsignedShortVec4;
         public GLTFBufferView bufferViewFloatVec2;
         public GLTFBufferView bufferViewImage;
         public GLTFBufferView bufferViewAnimationFloatScalar;
@@ -90,6 +96,7 @@ namespace GLTFExport.Entities
             ImagesList = new List<GLTFImage>();
             SamplersList = new List<GLTFSampler>();
             AnimationsList = new List<GLTFAnimation>();
+            SkinsList = new List<GLTFSkin>();
         }
 
         public void Prepare()
@@ -142,6 +149,10 @@ namespace GLTFExport.Entities
             {
                 animations = AnimationsList.ToArray();
             }
+            if (SkinsList.Count > 0)
+            {
+                skins = SkinsList.ToArray();
+            }
         }
     }
 }

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

@@ -51,6 +51,7 @@
     <Compile Include="GLTFCameraPerspective.cs" />
     <Compile Include="GLTFCameraOrthographic.cs" />
     <Compile Include="GLTFCamera.cs" />
+    <Compile Include="GLTFSkin.cs" />
     <Compile Include="GLTFMorphTarget.cs" />
     <Compile Include="GLTFSampler.cs" />
     <Compile Include="GLTFIndexedChildRootProperty.cs" />

+ 4 - 0
Exporters/3ds Max/GltfExport.Entities/GLTFMesh.cs

@@ -13,5 +13,9 @@ namespace GLTFExport.Entities
 
         // Identifier shared between a babylon mesh and its instances
         public int idGroupInstance;
+		// Babylon stores reference to skeleton inside a mesh
+		// While glTF stores it inside a node
+        // Identifier of the skeleton temporary stored here for transition
+        public int? idBabylonSkeleton;
     }
 }

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

@@ -36,6 +36,9 @@ namespace GLTFExport.Entities
 
         public List<int> ChildrenList { get; private set; }
 
+        // Used to compute transform world matrix
+        public GLTFNode parent;
+
         public GLTFNode()
         {
             ChildrenList = new List<int>();

+ 27 - 0
Exporters/3ds Max/GltfExport.Entities/GLTFSkin.cs

@@ -0,0 +1,27 @@
+using System.Runtime.Serialization;
+
+namespace GLTFExport.Entities
+{
+    [DataContract]
+    public class GLTFSkin : GLTFIndexedChildRootProperty
+    {
+        /// <summary>
+        /// The index of the accessor containing the floating-point 4x4 inverse-bind matrices.
+        /// The default is that each matrix is a 4x4 identity matrix, which implies that inverse-bind matrices were pre-applied.
+        /// </summary>
+        [DataMember(EmitDefaultValue = false)]
+        public int? inverseBindMatrices { get; set; }
+
+        /// <summary>
+        /// The index of the node used as a skeleton root. When undefined, joints transforms resolve to scene root.
+        /// </summary>
+        [DataMember(EmitDefaultValue = false)]
+        public int? skeleton { get; set; }
+
+        /// <summary>
+        /// Indices of skeleton nodes, used as joints in this skin.
+        /// </summary>
+        [DataMember(IsRequired = true)]
+        public int[] joints { get; set; }
+    }
+}

binární
Exporters/3ds Max/Max2Babylon-0.18.0.zip


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

@@ -224,6 +224,9 @@
     <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Animation.cs">
       <Link>Exporter\BabylonExporter.GLTFExporter.Animation.cs</Link>
     </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Skin.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Skin.cs</Link>
+    </Compile>
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
       <AutoGen>True</AutoGen>

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

@@ -224,6 +224,9 @@
     <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Animation.cs">
       <Link>Exporter\BabylonExporter.GLTFExporter.Animation.cs</Link>
     </Compile>
+    <Compile Include="..\Exporter\BabylonExporter.GLTFExporter.Skin.cs">
+      <Link>Exporter\BabylonExporter.GLTFExporter.Skin.cs</Link>
+    </Compile>
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\Resources.Designer.cs">
       <AutoGen>True</AutoGen>

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

@@ -263,6 +263,11 @@ namespace Max2Babylon
             ExportAnimation(property, animations, extractValueFunc, BabylonAnimation.DataType.Float);
         }
 
+        private static BabylonAnimation ExportMatrixAnimation(string property, Func<int, float[]> extractValueFunc, bool removeLinearAnimationKeys = true)
+        {
+            return ExportAnimation(property, extractValueFunc, BabylonAnimation.DataType.Matrix, removeLinearAnimationKeys);
+        }
+
         static void RemoveLinearAnimationKeys(List<BabylonAnimationKey> keys)
         {
             for (int ixFirst = keys.Count - 3; ixFirst >= 0; --ixFirst)
@@ -313,9 +318,18 @@ namespace Max2Babylon
 
         }
 
-        private static void ExportAnimation(string property, List<BabylonAnimation> animations, Func<int, float[]> extractValueFunc, BabylonAnimation.DataType dataType)
+        private static void ExportAnimation(string property, List<BabylonAnimation> animations, Func<int, float[]> extractValueFunc, BabylonAnimation.DataType dataType, bool removeLinearAnimationKeys = true)
+        {
+            var babylonAnimation = ExportAnimation(property, extractValueFunc, dataType, removeLinearAnimationKeys);
+            if (babylonAnimation != null)
+            {
+                animations.Add(babylonAnimation);
+            }
+        }
+
+        private static BabylonAnimation ExportAnimation(string property, Func<int, float[]> extractValueFunc, BabylonAnimation.DataType dataType, bool removeLinearAnimationKeys = true)
         {
-            var exportNonOptimizedAnimations = Loader.Core.RootNode.GetBoolProperty("babylonjs_exportnonoptimizedanimations");
+            var optimizeAnimations = !Loader.Core.RootNode.GetBoolProperty("babylonjs_donotoptimizeanimations"); // reverse negation for clarity
 
             var start = Loader.Core.AnimRange.Start;
             var end = Loader.Core.AnimRange.End;
@@ -326,7 +340,9 @@ namespace Max2Babylon
             {
                 var current = extractValueFunc(key);
 
-                if (exportNonOptimizedAnimations && previous != null && previous.IsEqualTo(current))
+                // if animations are not optimized, all keys from start to end are created
+                // otherwise, keys are added only for non constant values
+                if (optimizeAnimations && previous != null && previous.IsEqualTo(current))
                 {
                     continue; // Do not add key
                 }
@@ -340,7 +356,8 @@ namespace Max2Babylon
                 previous = current;
             }
 
-            if (!exportNonOptimizedAnimations)
+            // if animations are not optimized, do the following as minimal optimization
+            if (removeLinearAnimationKeys && !optimizeAnimations)
             {
                 RemoveLinearAnimationKeys(keys);
             }
@@ -359,7 +376,6 @@ namespace Max2Babylon
 
                 if (animationPresent)
                 {
-
                     if (keys[keys.Count - 1].frame != end / Ticks)
                     {
                         keys.Add(new BabylonAnimationKey()
@@ -378,10 +394,10 @@ namespace Max2Babylon
                         loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle,
                         property = property
                     };
-
-                    animations.Add(babylonAnimation);
+                    return babylonAnimation;
                 }
             }
+            return null;
         }
     }
 }

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

@@ -20,6 +20,7 @@ namespace Max2Babylon
             {
                 RaiseMessage("GLTFExporter.AbstractMesh | Add " + babylonAbstractMesh.name + " as child to " + gltfParentNode.name, 2);
                 gltfParentNode.ChildrenList.Add(gltfNode.index);
+                gltfNode.parent = gltfParentNode;
             }
             else
             {
@@ -53,6 +54,16 @@ namespace Max2Babylon
             if (gltfMesh != null)
             {
                 gltfNode.mesh = gltfMesh.index;
+                
+                // Skin
+                if (gltfMesh.idBabylonSkeleton.HasValue)
+                {
+                    var babylonSkeleton = babylonScene.skeletons[gltfMesh.idBabylonSkeleton.Value];
+                    // Export a new skin and a new skeleton
+                    // TODO - Use the skeleton if already exported and only create a new skin
+                    var gltfSkin = ExportSkin(babylonSkeleton, gltf, gltfNode);
+                    gltfNode.skin = gltfSkin.index;
+                }
             }
 
             // Animations

+ 166 - 54
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Animation.cs

@@ -7,7 +7,7 @@ namespace Max2Babylon
 {
     partial class BabylonExporter
     {
-        private static float FPS_FACTOR = 60.0f; // TODO - Which FPS factor ?
+        private static float FPS_FACTOR = 30.0f; // TODO - Which FPS factor ?
 
         private GLTFAnimation ExportNodeAnimation(BabylonNode babylonNode, GLTF gltf, GLTFNode gltfNode, BabylonScene babylonScene = null)
         {
@@ -34,62 +34,11 @@ namespace Max2Babylon
                         continue;
                     }
 
-                    // Buffer
-                    var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
-
                     // --- Input ---
-                    var accessorInput = GLTFBufferService.Instance.CreateAccessor(
-                        gltf,
-                        GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
-                        "accessorAnimationInput",
-                        GLTFAccessor.ComponentType.FLOAT,
-                        GLTFAccessor.TypeEnum.SCALAR
-                    );
-                    // Populate accessor
-                    accessorInput.min = new float[] { float.MaxValue };
-                    accessorInput.max = new float[] { float.MinValue };
-                    foreach (var babylonAnimationKey in babylonAnimation.keys)
-                    {
-                        var inputValue = babylonAnimationKey.frame / FPS_FACTOR;
-                        // Store values as bytes
-                        accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
-                        // Update min and max values
-                        GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
-                    };
-                    accessorInput.count = babylonAnimation.keys.Length;
+                    var accessorInput = _createAndPopulateInput(gltf, babylonAnimation);
 
                     // --- Output ---
-                    GLTFAccessor accessorOutput = null;
-                    switch (gltfTarget.path)
-                    {
-                        case "translation":
-                            accessorOutput = GLTFBufferService.Instance.CreateAccessor(
-                                gltf,
-                                GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
-                                "accessorAnimationPositions",
-                                GLTFAccessor.ComponentType.FLOAT,
-                                GLTFAccessor.TypeEnum.VEC3
-                            );
-                            break;
-                        case "rotation":
-                            accessorOutput = GLTFBufferService.Instance.CreateAccessor(
-                                gltf,
-                                GLTFBufferService.Instance.GetBufferViewAnimationFloatVec4(gltf, buffer),
-                                "accessorAnimationRotations",
-                                GLTFAccessor.ComponentType.FLOAT,
-                                GLTFAccessor.TypeEnum.VEC4
-                            );
-                            break;
-                        case "scale":
-                            accessorOutput = GLTFBufferService.Instance.CreateAccessor(
-                                gltf,
-                                GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
-                                "accessorAnimationScales",
-                                GLTFAccessor.ComponentType.FLOAT,
-                                GLTFAccessor.TypeEnum.VEC3
-                            );
-                            break;
-                    }
+                    GLTFAccessor accessorOutput = _createAccessorOfPath(gltfTarget.path, gltf);
                     // Populate accessor
                     foreach (var babylonAnimationKey in babylonAnimation.keys)
                     {
@@ -151,6 +100,169 @@ namespace Max2Babylon
             }
         }
 
+        private GLTFAnimation ExportBoneAnimation(BabylonBone babylonBone, GLTF gltf, GLTFNode gltfNode)
+        {
+            var channelList = new List<GLTFChannel>();
+            var samplerList = new List<GLTFAnimationSampler>();
+
+            if (babylonBone.animation != null && babylonBone.animation.property == "_matrix")
+            {
+                RaiseMessage("GLTFExporter.Animation | Export animation of bone named: " + babylonBone.name, 2);
+
+                var babylonAnimation = babylonBone.animation;
+
+                // --- Input ---
+                var accessorInput = _createAndPopulateInput(gltf, babylonAnimation);
+
+                // --- Output ---
+                var paths = new string[] { "translation", "rotation", "scale" };
+                var accessorOutputByPath = new Dictionary<string, GLTFAccessor>();
+
+                foreach (string path in paths)
+                {
+                    GLTFAccessor accessorOutput = _createAccessorOfPath(path, gltf);
+                    accessorOutputByPath.Add(path, accessorOutput);
+                }
+
+                // Populate accessors
+                foreach (var babylonAnimationKey in babylonAnimation.keys)
+                {
+                    var matrix = new BabylonMatrix();
+                    matrix.m = babylonAnimationKey.values;
+
+                    var translationBabylon = new BabylonVector3();
+                    var rotationQuatBabylon = new BabylonQuaternion();
+                    var scaleBabylon = new BabylonVector3();
+                    matrix.decompose(scaleBabylon, rotationQuatBabylon, translationBabylon);
+
+                    var outputValuesByPath = new Dictionary<string, float[]>();
+                    outputValuesByPath.Add("translation", translationBabylon.ToArray());
+                    outputValuesByPath.Add("rotation", rotationQuatBabylon.ToArray());
+                    outputValuesByPath.Add("scale", scaleBabylon.ToArray());
+
+                    // Store values as bytes
+                    foreach (string path in paths)
+                    {
+                        var accessorOutput = accessorOutputByPath[path];
+                        var outputValues = outputValuesByPath[path];
+                        foreach (var outputValue in outputValues)
+                        {
+                            accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
+                        }
+                        accessorOutput.count++;
+                    }
+                };
+
+                foreach (string path in paths)
+                {
+                    var accessorOutput = accessorOutputByPath[path];
+
+                    // Animation sampler
+                    var gltfAnimationSampler = new GLTFAnimationSampler
+                    {
+                        input = accessorInput.index,
+                        output = accessorOutput.index
+                    };
+                    gltfAnimationSampler.index = samplerList.Count;
+                    samplerList.Add(gltfAnimationSampler);
+
+                    // Target
+                    var gltfTarget = new GLTFChannelTarget
+                    {
+                        node = gltfNode.index
+                    };
+                    gltfTarget.path = path;
+
+                    // Channel
+                    var gltfChannel = new GLTFChannel
+                    {
+                        sampler = gltfAnimationSampler.index,
+                        target = gltfTarget
+                    };
+                    channelList.Add(gltfChannel);
+                }
+            }
+
+            // Do not export empty arrays
+            if (channelList.Count > 0)
+            {
+                // Animation
+                var gltfAnimation = new GLTFAnimation
+                {
+                    channels = channelList.ToArray(),
+                    samplers = samplerList.ToArray()
+                };
+                gltf.AnimationsList.Add(gltfAnimation);
+                return gltfAnimation;
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        private GLTFAccessor _createAndPopulateInput(GLTF gltf, BabylonAnimation babylonAnimation)
+        {
+            var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
+            var accessorInput = GLTFBufferService.Instance.CreateAccessor(
+                gltf,
+                GLTFBufferService.Instance.GetBufferViewAnimationFloatScalar(gltf, buffer),
+                "accessorAnimationInput",
+                GLTFAccessor.ComponentType.FLOAT,
+                GLTFAccessor.TypeEnum.SCALAR
+            );
+            // Populate accessor
+            accessorInput.min = new float[] { float.MaxValue };
+            accessorInput.max = new float[] { float.MinValue };
+            foreach (var babylonAnimationKey in babylonAnimation.keys)
+            {
+                var inputValue = babylonAnimationKey.frame / FPS_FACTOR;
+                // Store values as bytes
+                accessorInput.bytesList.AddRange(BitConverter.GetBytes(inputValue));
+                // Update min and max values
+                GLTFBufferService.UpdateMinMaxAccessor(accessorInput, inputValue);
+            };
+            accessorInput.count = babylonAnimation.keys.Length;
+            return accessorInput;
+        }
+
+        private GLTFAccessor _createAccessorOfPath(string path, GLTF gltf)
+        {
+            var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
+            GLTFAccessor accessorOutput = null;
+            switch (path)
+            {
+                case "translation":
+                    accessorOutput = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
+                        "accessorAnimationPositions",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.VEC3
+                    );
+                    break;
+                case "rotation":
+                    accessorOutput = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewAnimationFloatVec4(gltf, buffer),
+                        "accessorAnimationRotations",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.VEC4
+                    );
+                    break;
+                case "scale":
+                    accessorOutput = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewAnimationFloatVec3(gltf, buffer),
+                        "accessorAnimationScales",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.VEC3
+                    );
+                    break;
+            }
+            return accessorOutput;
+        }
+
         private bool ExportMorphTargetWeightAnimation(BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFNode gltfNode, List<GLTFChannel> channelList, List<GLTFAnimationSampler> samplerList)
         {
             if (!_isBabylonMorphTargetManagerAnimationValid(babylonMorphTargetManager))

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

@@ -25,6 +25,7 @@ namespace Max2Babylon
             {
                 RaiseMessage("GLTFExporter.Camera | Add " + babylonCamera.name + " as child to " + gltfParentNode.name, 3);
                 gltfParentNode.ChildrenList.Add(gltfNode.index);
+                gltfNode.parent = gltfParentNode;
             }
             else
             {

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

@@ -25,6 +25,7 @@ namespace Max2Babylon
             {
                 RaiseMessage("GLTFExporter.Light | Add " + babylonLight.name + " as child to " + gltfParentNode.name, 3);
                 gltfParentNode.ChildrenList.Add(gltfNode.index);
+                gltfNode.parent = gltfParentNode;
             }
             else
             {

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

@@ -44,12 +44,30 @@ namespace Max2Babylon
                 {
                     RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture=null", 3);
                 }
+                else
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture.name=" + babylonStandardMaterial.diffuseTexture.name, 3);
+                }
 
                 // Normal / bump
                 if (babylonStandardMaterial.bumpTexture == null)
                 {
                     RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.bumpTexture=null", 3);
                 }
+                else
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.bumpTexture.name=" + babylonStandardMaterial.bumpTexture.name, 3);
+                }
+
+                // Opacity
+                if (babylonStandardMaterial.opacityTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.opacityTexture=null", 3);
+                }
+                else
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.opacityTexture.name=" + babylonStandardMaterial.opacityTexture.name, 3);
+                }
 
                 // Specular
                 for (int i = 0; i < babylonStandardMaterial.specular.Length; i++)
@@ -57,12 +75,24 @@ namespace Max2Babylon
                     RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specular[" + i + "]=" + babylonStandardMaterial.specular[i], 3);
                 }
                 RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 3);
+                if (babylonStandardMaterial.specularTexture == null)
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specularTexture=null", 3);
+                }
+                else
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.specularTexture.name=" + babylonStandardMaterial.specularTexture.name, 3);
+                }
 
                 // Occlusion
                 if (babylonStandardMaterial.ambientTexture == null)
                 {
                     RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.ambientTexture=null", 3);
                 }
+                else
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.ambientTexture.name=" + babylonStandardMaterial.ambientTexture.name, 3);
+                }
 
                 // Emissive
                 for (int i = 0; i < babylonStandardMaterial.emissive.Length; i++)
@@ -73,6 +103,10 @@ namespace Max2Babylon
                 {
                     RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture=null", 3);
                 }
+                else
+                {
+                    RaiseMessage("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture.name=" + babylonStandardMaterial.emissiveTexture.name, 3);
+                }
 
 
                 // --------------------------------

+ 65 - 41
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Mesh.cs

@@ -29,38 +29,55 @@ namespace Max2Babylon
             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;
+            bool hasBones = babylonMesh.matricesIndices != null && babylonMesh.matricesIndices.Length > 0;
 
             RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 3);
             RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 3);
             RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 3);
             RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 3);
+            RaiseMessage("GLTFExporter.Mesh | hasBones=" + hasBones, 3);
 
             // Retreive vertices data from babylon mesh
             List<GLTFGlobalVertex> globalVertices = new List<GLTFGlobalVertex>();
             for (int indexVertex = 0; indexVertex < nbVertices; indexVertex++)
             {
                 GLTFGlobalVertex globalVertex = new GLTFGlobalVertex();
-                globalVertex.Position = createIPoint3(babylonMesh.positions, indexVertex);
-                // Switch from left to right handed coordinate system
-                //globalVertex.Position.X *= -1;
-                globalVertex.Normal = createIPoint3(babylonMesh.normals, indexVertex);
+                globalVertex.Position = Tools.CreateIPoint3FromArray(babylonMesh.positions, indexVertex);
+                globalVertex.Normal = Tools.CreateIPoint3FromArray(babylonMesh.normals, indexVertex);
                 if (hasUV)
                 {
-                    globalVertex.UV = createIPoint2(babylonMesh.uvs, indexVertex);
+                    globalVertex.UV = Tools.CreateIPoint2FromArray(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, indexVertex);
+                    globalVertex.UV2 = Tools.CreateIPoint2FromArray(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, indexVertex).ToArray();
+                    globalVertex.Color = Tools.CreateIPoint4FromArray(babylonMesh.colors, indexVertex).ToArray();
+                }
+                if (hasBones)
+                {
+					// In babylon, the 4 bones indices are stored in a single int
+					// Each bone index is 8-bit offset from the next
+                    int bonesIndicesMerged = babylonMesh.matricesIndices[indexVertex];
+                    int bone3 = bonesIndicesMerged >> 24;
+                    bonesIndicesMerged -= bone3 << 24;
+                    int bone2 = bonesIndicesMerged >> 16;
+                    bonesIndicesMerged -= bone2 << 16;
+                    int bone1 = bonesIndicesMerged >> 8;
+                    bonesIndicesMerged -= bone1 << 8;
+                    int bone0 = bonesIndicesMerged >> 0;
+                    bonesIndicesMerged -= bone0 << 0;
+                    var bonesIndicesArray = new ushort[] { (ushort)bone0, (ushort)bone1, (ushort)bone2, (ushort)bone3 };
+                    globalVertex.BonesIndices = bonesIndicesArray;
+                    globalVertex.BonesWeights = Tools.CreateIPoint4FromArray(babylonMesh.matricesWeights, indexVertex).ToArray();
                 }
 
                 globalVertices.Add(globalVertex);
@@ -81,7 +98,10 @@ namespace Max2Babylon
             gltfMesh.index = gltf.MeshesList.Count;
             gltf.MeshesList.Add(gltfMesh);
             gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
-            var weights = new List<float>();
+            if (hasBones)
+            {
+                gltfMesh.idBabylonSkeleton = babylonMesh.skeletonId;
+            }
 
             // --------------------------
             // ---- glTF primitives -----
@@ -89,7 +109,7 @@ namespace Max2Babylon
 
             RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2);
             var meshPrimitives = new List<GLTFMeshPrimitive>();
-            
+            var weights = new List<float>();
             foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes)
             {
                 // --------------------------
@@ -260,6 +280,38 @@ namespace Max2Babylon
                     accessorUV2s.count = globalVerticesSubMesh.Count;
                 }
 
+                // --- Bones ---
+                if (hasBones)
+                {
+                    // --- Joints ---
+                    var accessorJoints = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer),
+                        "accessorJoints",
+                        GLTFAccessor.ComponentType.UNSIGNED_SHORT,
+                        GLTFAccessor.TypeEnum.VEC4
+                    );
+                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), accessorJoints.index);
+                    // Populate accessor
+                    List<ushort> joints = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndices[0], v.BonesIndices[1], v.BonesIndices[2], v.BonesIndices[3] }).ToList();
+                    joints.ForEach(n => accessorJoints.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    accessorJoints.count = globalVerticesSubMesh.Count;
+
+                    // --- Weights ---
+                    var accessorWeights = GLTFBufferService.Instance.CreateAccessor(
+                        gltf,
+                        GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
+                        "accessorWeights",
+                        GLTFAccessor.ComponentType.FLOAT,
+                        GLTFAccessor.TypeEnum.VEC4
+                    );
+                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), accessorWeights.index);
+                    // Populate accessor
+                    List<float> weightBones = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeights[0], v.BonesWeights[1], v.BonesWeights[2], v.BonesWeights[3] }).ToList();
+                    weightBones.ForEach(n => accessorWeights.bytesList.AddRange(BitConverter.GetBytes(n)));
+                    accessorWeights.count = globalVerticesSubMesh.Count;
+                }
+
                 // Morph targets
                 var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);
                 if (babylonMorphTargetManager != null)
@@ -321,10 +373,10 @@ namespace Max2Babylon
                     accessorTargetPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
                     for (int indexPosition = 0; indexPosition < babylonMorphTarget.positions.Length; indexPosition += 3)
                     {
-                        var positionTarget = _subArray(babylonMorphTarget.positions, indexPosition, 3);
+                        var positionTarget = Tools.SubArray(babylonMorphTarget.positions, indexPosition, 3);
 
                         // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
-                        var positionMesh = _subArray(babylonMesh.positions, indexPosition, 3);
+                        var positionMesh = Tools.SubArray(babylonMesh.positions, indexPosition, 3);
                         for (int indexCoordinate = 0; indexCoordinate < positionTarget.Length; indexCoordinate++)
                         {
                             positionTarget[indexCoordinate] = positionTarget[indexCoordinate] - positionMesh[indexCoordinate];
@@ -355,10 +407,10 @@ namespace Max2Babylon
                     // Populate accessor
                     for (int indexNormal = 0; indexNormal < babylonMorphTarget.positions.Length; indexNormal += 3)
                     {
-                        var normalTarget = _subArray(babylonMorphTarget.normals, indexNormal, 3);
+                        var normalTarget = Tools.SubArray(babylonMorphTarget.normals, indexNormal, 3);
 
                         // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive
-                        var normalMesh = _subArray(babylonMesh.normals, indexNormal, 3);
+                        var normalMesh = Tools.SubArray(babylonMesh.normals, indexNormal, 3);
                         for (int indexCoordinate = 0; indexCoordinate < normalTarget.Length; indexCoordinate++)
                         {
                             normalTarget[indexCoordinate] = normalTarget[indexCoordinate] - normalMesh[indexCoordinate];
@@ -384,33 +436,5 @@ namespace Max2Babylon
                 meshPrimitive.targets = gltfMorphTargets.ToArray();
             }
         }
-
-        private float[] _subArray(float[] array, int startIndex, int count)
-        {
-            var result = new float[count];
-            for (int i = 0; i < count; i++)
-            {
-                result[i] = array[startIndex + i];
-            }
-            return result;
-        }
-
-        private IPoint2 createIPoint2(float[] array, int index)
-        {
-            var startIndex = index * 2;
-            return Loader.Global.Point2.Create(array[startIndex], array[startIndex + 1]);
-        }
-
-        private IPoint3 createIPoint3(float[] array, int index)
-        {
-            var startIndex = index * 3;
-            return Loader.Global.Point3.Create(array[startIndex], array[startIndex + 1], array[startIndex + 2]);
-        }
-
-        private IPoint4 createIPoint4(float[] array, int index)
-        {
-            var startIndex = index * 4;
-            return Loader.Global.Point4.Create(array[startIndex], array[startIndex + 1], array[startIndex + 2], array[startIndex + 3]);
-        }
     }
 }

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

@@ -0,0 +1,202 @@
+using BabylonExport.Entities;
+using GLTFExport.Entities;
+using System;
+using System.Collections.Generic;
+
+namespace Max2Babylon
+{
+    partial class BabylonExporter
+    {
+        // Bones stored in BabylonSkeleton array are not assumed to be tree-ordered
+        // Meaning, first element could be a leaf thus resulting in exporting all its ancestors before himself
+        // Store bones already exported to prevent multiple exportation of same bone
+        private Dictionary<BabylonBone, GLTFNode> alreadyExportedBones;
+
+        private GLTFSkin ExportSkin(BabylonSkeleton babylonSkeleton, GLTF gltf, GLTFNode gltfNode)
+        {
+            // Skin
+            GLTFSkin gltfSkin = new GLTFSkin
+            {
+                name = babylonSkeleton.name
+            };
+            gltfSkin.index = gltf.SkinsList.Count;
+            gltf.SkinsList.Add(gltfSkin);
+
+            var bones = new List<BabylonBone>(babylonSkeleton.bones);
+
+            // Compute and store world matrix of each bone
+            var bonesWorldMatrices = new Dictionary<int, BabylonMatrix>();
+            foreach (var babylonBone in babylonSkeleton.bones)
+            {
+                if (!bonesWorldMatrices.ContainsKey(babylonBone.index))
+                {
+                    BabylonMatrix boneWorldMatrix = _getBoneWorldMatrix(babylonBone, bones);
+                    bonesWorldMatrices.Add(babylonBone.index, boneWorldMatrix);
+                }
+            }
+
+            // Buffer
+            var buffer = GLTFBufferService.Instance.GetBuffer(gltf);
+
+            // Accessor - InverseBindMatrices
+            var accessorInverseBindMatrices = GLTFBufferService.Instance.CreateAccessor(
+                gltf,
+                GLTFBufferService.Instance.GetBufferViewFloatMat4(gltf, buffer),
+                "accessorInverseBindMatrices",
+                GLTFAccessor.ComponentType.FLOAT,
+                GLTFAccessor.TypeEnum.MAT4
+            );
+            gltfSkin.inverseBindMatrices = accessorInverseBindMatrices.index;
+
+            // World matrix of the node
+            var invNodeWorldMatrix = BabylonMatrix.Invert(_getNodeWorldMatrix(gltfNode)); // inverted
+
+            var gltfJoints = new List<int>();
+            alreadyExportedBones = new Dictionary<BabylonBone, GLTFNode>();
+            foreach (var babylonBone in babylonSkeleton.bones)
+            {
+                // Export bone as a new node
+                var gltfBoneNode = _exportBone(babylonBone, gltf, babylonSkeleton, bones);
+                gltfJoints.Add(gltfBoneNode.index);
+
+                // Set this bone as skeleton if it is a root
+                if (babylonBone.parentBoneIndex == -1)
+                {
+                    gltfSkin.skeleton = gltfBoneNode.index;
+                }
+                // Compute inverseBindMatrice for this bone when attached to this node
+                var boneLocalMatrix = new BabylonMatrix();
+                boneLocalMatrix.m = babylonBone.matrix;
+
+                BabylonMatrix boneWorldMatrix = null;
+                if (babylonBone.parentBoneIndex == -1)
+                {
+                    boneWorldMatrix = boneLocalMatrix;
+                }
+                else
+                {
+                    var parentWorldMatrix = bonesWorldMatrices[babylonBone.parentBoneIndex];
+                    // Remove scale of parent
+                    // This actually enable to take into account the scale of the bones, except for the root one
+                    parentWorldMatrix = _removeScale(parentWorldMatrix);
+
+                    boneWorldMatrix = boneLocalMatrix * parentWorldMatrix;
+                }
+
+                var inverseBindMatrices = BabylonMatrix.Invert(boneWorldMatrix * invNodeWorldMatrix);
+
+                // Populate accessor
+                List<float> matrix = new List<float>(inverseBindMatrices.m);
+                matrix.ForEach(n => accessorInverseBindMatrices.bytesList.AddRange(BitConverter.GetBytes(n)));
+                accessorInverseBindMatrices.count++;
+            }
+            gltfSkin.joints = gltfJoints.ToArray();
+
+            return gltfSkin;
+        }
+
+        private GLTFNode _exportBone(BabylonBone babylonBone, GLTF gltf, BabylonSkeleton babylonSkeleton, List<BabylonBone> bones)
+        {
+            if (alreadyExportedBones.ContainsKey(babylonBone))
+            {
+                return alreadyExportedBones[babylonBone];
+            }
+
+            // Node
+            var gltfNode = new GLTFNode
+            {
+                name = babylonBone.name
+            };
+            gltfNode.index = gltf.NodesList.Count;
+            gltf.NodesList.Add(gltfNode);
+            alreadyExportedBones.Add(babylonBone, gltfNode);
+
+            // Hierarchy
+            if (babylonBone.parentBoneIndex >= 0)
+            {
+                var babylonParentBone = bones.Find(_babylonBone => _babylonBone.index == babylonBone.parentBoneIndex);
+                var gltfParentNode = _exportBone(babylonParentBone, gltf, babylonSkeleton, bones);
+                RaiseMessage("GLTFExporter.Skin | Add " + babylonBone.name + " as child to " + gltfParentNode.name, 2);
+                gltfParentNode.ChildrenList.Add(gltfNode.index);
+                gltfNode.parent = gltfParentNode;
+            }
+            else
+            {
+                // It's a root node
+                // Only root nodes are listed in a gltf scene
+                RaiseMessage("GLTFExporter.Skin | Add " + babylonBone.name + " as root node to scene", 2);
+                gltf.scenes[0].NodesList.Add(gltfNode.index);
+            }
+
+            // Transform
+            // Bones transform are exported through translation/rotation/scale rather than matrix
+            // Because gltf node animation can only target TRS properties, not the matrix one
+            // Create matrix from array
+            var babylonMatrix = new BabylonMatrix();
+            babylonMatrix.m = babylonBone.matrix;
+            // Decompose matrix into TRS
+            var translationBabylon = new BabylonVector3();
+            var rotationQuatBabylon = new BabylonQuaternion();
+            var scaleBabylon = new BabylonVector3();
+            babylonMatrix.decompose(scaleBabylon, rotationQuatBabylon, translationBabylon);
+            // Store TRS values
+            gltfNode.translation = translationBabylon.ToArray();
+            gltfNode.rotation = rotationQuatBabylon.ToArray();
+            gltfNode.scale = scaleBabylon.ToArray();
+
+            // Animations
+            ExportBoneAnimation(babylonBone, gltf, gltfNode);
+
+            return gltfNode;
+        }
+
+        private BabylonMatrix _getNodeLocalMatrix(GLTFNode gltfNode)
+        {
+            return BabylonMatrix.Compose(
+                BabylonVector3.FromArray(gltfNode.scale),
+                BabylonQuaternion.FromArray(gltfNode.rotation),
+                BabylonVector3.FromArray(gltfNode.translation)
+            );
+        }
+
+        private BabylonMatrix _getNodeWorldMatrix(GLTFNode gltfNode)
+        {
+            if (gltfNode.parent == null)
+            {
+                return _getNodeLocalMatrix(gltfNode);
+            }
+            else
+            {
+                return _getNodeLocalMatrix(gltfNode) * _getNodeWorldMatrix(gltfNode.parent);
+            }
+        }
+
+        private BabylonMatrix _getBoneWorldMatrix(BabylonBone babylonBone, List<BabylonBone> bones)
+        {
+            var boneLocalMatrix = new BabylonMatrix();
+            boneLocalMatrix.m = babylonBone.matrix;
+            if (babylonBone.parentBoneIndex == -1)
+            {
+                return boneLocalMatrix;
+            }
+            else
+            {
+                var parentBabylonBone = bones.Find(bone => bone.index == babylonBone.parentBoneIndex);
+                var parentWorldMatrix = _getBoneWorldMatrix(parentBabylonBone, bones);
+                return boneLocalMatrix * parentWorldMatrix;
+            }
+        }
+
+        private BabylonMatrix _removeScale(BabylonMatrix boneWorldMatrix)
+        {
+            var translation = new BabylonVector3();
+            var rotation = new BabylonQuaternion();
+            var scale = new BabylonVector3();
+            boneWorldMatrix.decompose(scale, rotation, translation);
+            scale.X = 1;
+            scale.Y = 1;
+            scale.Z = 1;
+            return BabylonMatrix.Compose(scale, rotation, translation);
+        }
+    }
+}

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

@@ -263,7 +263,7 @@ namespace Max2Babylon
             }
             else if (type == typeof(BabylonLight))
             {
-                if (isNodeRelevantToExport(babylonNode, babylonScene))
+                if (isNodeRelevantToExport(babylonNode))
                 {
                     // 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);
@@ -285,12 +285,12 @@ namespace Max2Babylon
             if (gltfNode != null)
             {
                 // ...export its children
-                List<BabylonNode> babylonDescendants = getDescendants(babylonNode, babylonScene);
+                List<BabylonNode> babylonDescendants = getDescendants(babylonNode);
                 babylonDescendants.ForEach(descendant => exportNodeRec(descendant, gltf, babylonScene, gltfNode));
             }
         }
 
-        private List<BabylonNode> getDescendants(BabylonNode babylonNode, BabylonScene babylonScene)
+        private List<BabylonNode> getDescendants(BabylonNode babylonNode)
         {
             return babylonNodes.FindAll(node => node.parentId == babylonNode.id);
         }
@@ -298,7 +298,7 @@ namespace Max2Babylon
         /// <summary>
         /// Return true if node descendant hierarchy has any Mesh or Camera to export
         /// </summary>
-        private bool isNodeRelevantToExport(BabylonNode babylonNode, BabylonScene babylonScene)
+        private bool isNodeRelevantToExport(BabylonNode babylonNode)
         {
             var type = babylonNode.GetType();
             if (type == typeof(BabylonAbstractMesh) ||
@@ -309,11 +309,11 @@ namespace Max2Babylon
             }
 
             // Descandant recursivity
-            List<BabylonNode> babylonDescendants = getDescendants(babylonNode, babylonScene);
+            List<BabylonNode> babylonDescendants = getDescendants(babylonNode);
             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))
+                if (isNodeRelevantToExport(babylonDescendants[indexDescendant]))
                 {
                     return true;
                 }

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

@@ -124,9 +124,6 @@ namespace Max2Babylon
             
             // Position / rotation / scaling / hierarchy
             exportNode(babylonMesh, meshNode, scene, babylonScene);
-
-            // Animations
-            exportAnimation(babylonMesh, meshNode);
             
             // Sounds
             var soundName = meshNode.MaxNode.GetStringProperty("babylonjs_sound_filename", "");
@@ -406,27 +403,32 @@ namespace Max2Babylon
                         {
                             // Morph target
                             var maxMorphTarget = morpher.GetMorphTarget(i);
-                            var babylonMorphTarget = new BabylonMorphTarget
-                            {
-                                name = maxMorphTarget.Name
-                            };
-                            babylonMorphTargets.Add(babylonMorphTarget);
-
-                            // TODO - Influence
-                            babylonMorphTarget.influence = 0f;
-
-                            // Target geometry
-                            var targetVertices = ExtractVertices(maxMorphTarget, optimizeVertices);
-                            babylonMorphTarget.positions = targetVertices.SelectMany(v => new[] { v.Position.X, v.Position.Y, v.Position.Z }).ToArray();
-                            babylonMorphTarget.normals = targetVertices.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToArray();
-
-                            // Animations
-                            var animations = new List<BabylonAnimation>();
-                            var morphWeight = morpher.GetMorphWeight(i);
-                            ExportFloatGameController(morphWeight, "influence", animations);
-                            if (animations.Count > 0)
+
+                            // Ensure target still exists (green color legend)
+                            if (maxMorphTarget != null)
                             {
-                                babylonMorphTarget.animations = animations.ToArray();
+                                var babylonMorphTarget = new BabylonMorphTarget
+                                {
+                                    name = maxMorphTarget.Name
+                                };
+                                babylonMorphTargets.Add(babylonMorphTarget);
+
+                                // TODO - Influence
+                                babylonMorphTarget.influence = 0f;
+
+                                // Target geometry
+                                var targetVertices = ExtractVertices(maxMorphTarget, optimizeVertices);
+                                babylonMorphTarget.positions = targetVertices.SelectMany(v => new[] { v.Position.X, v.Position.Y, v.Position.Z }).ToArray();
+                                babylonMorphTarget.normals = targetVertices.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToArray();
+                                
+                                // Animations
+                                var animations = new List<BabylonAnimation>();
+                                var morphWeight = morpher.GetMorphWeight(i);
+                                ExportFloatGameController(morphWeight, "influence", animations);
+                                if (animations.Count > 0)
+                                {
+                                    babylonMorphTarget.animations = animations.ToArray();
+                                }
                             }
                         }
                     });
@@ -435,6 +437,10 @@ namespace Max2Babylon
                 }
             }
 
+            // Animations
+			// Done last to avoid '0 vertex found' error (unkown cause)
+            exportAnimation(babylonMesh, meshNode);
+
             babylonScene.MeshesList.Add(babylonMesh);
 
             return babylonMesh;

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

@@ -16,12 +16,6 @@ namespace Max2Babylon
         readonly List<IIGameSkin> skins = new List<IIGameSkin>();
         readonly List<IIGameNode> skinnedNodes = new List<IIGameNode>();
 
-        IGMatrix WithNoScale(IGMatrix mat)
-        {
-            var mat3 = mat.ExtractMatrix3();
-            mat3.NoScale();
-            return Loader.Global.GMatrix.Create(mat3);
-        }
         private void ExportSkin(IIGameSkin skin, BabylonScene babylonScene)
         {
             var babylonSkeleton = new BabylonSkeleton { id = skins.IndexOf(skin) };
@@ -56,9 +50,7 @@ namespace Max2Babylon
                 bindPoseInfos[sortedIndex] = (new BonePoseInfo { AbsoluteTransform = boneInitMatrix });
             }
 
-            // fix hierarchy an generate animation keys
-            var exportNonOptimizedAnimations = Loader.Core.RootNode.GetBoolProperty("babylonjs_exportnonoptimizedanimations");
-
+            // fix hierarchy and generate animation keys
             for (var index = 0; index < skin.TotalSkinBoneCount; index++)
             {
                 var gameBone = gameBones[index];
@@ -81,21 +73,7 @@ namespace Max2Babylon
 
                 babBone.matrix = bindPoseInfos[index].LocalTransform.ToArray();
 
-                var babylonAnimation = new BabylonAnimation
-                {
-                    name = gameBone.Name + "Animation",
-                    property = "_matrix",
-                    dataType = (int)BabylonAnimation.DataType.Matrix,
-                    loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle,
-                    framePerSecond = Loader.Global.FrameRate
-                };
-
-                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)
+                var babylonAnimation = ExportMatrixAnimation("_matrix", key =>
                 {
                     var objectTM = gameBone.GetObjectTM(key);
                     var parentNode = gameBone.NodeParent;
@@ -108,22 +86,15 @@ namespace Max2Babylon
                     {
                         mat = objectTM.Multiply(parentNode.GetObjectTM(key).Inverse);
                     }
+                    return mat.ToArray();
+                },
+                false); // Do not remove linear animation keys for bones
 
-                    var current = mat.ToArray();
-                    if (key == start || key == end || exportNonOptimizedAnimations || !(previous.IsEqualTo(current)))
-                    {
-                        keys.Add(new BabylonAnimationKey
-                        {
-                            frame = key / Ticks,
-                            values = current
-                        });
-                    }
-
-                    previous = current;
+                if (babylonAnimation != null)
+                {
+                    babylonAnimation.name = gameBone.Name + "Animation"; // override default animation name
+                    babBone.animation = babylonAnimation;
                 }
-
-                babylonAnimation.keys = keys.ToArray();
-                babBone.animation = babylonAnimation;
             }
 
             babylonSkeleton.needInitialSkinMatrix = true;

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

@@ -338,10 +338,10 @@ namespace Max2Babylon
             if (babylonNode == null &&
                 isNodeRelevantToExport(maxGameNode))
             {
-                if (!hasExporter)
-                {
-                    RaiseWarning($"Type '{maxGameNode.IGameObject.IGameType}' of node '{maxGameNode.Name}' has no exporter, an empty node is exported instead", 1);
-                }
+                //if (!hasExporter)
+                //{
+                //    RaiseWarning($"Type '{maxGameNode.IGameObject.IGameType}' of node '{maxGameNode.Name}' has no exporter, an empty node is exported instead", 1);
+                //}
                 // Create a dummy (empty mesh)
                 babylonNode = ExportDummy(maxGameScene, maxGameNode, babylonScene);
             };

+ 21 - 0
Exporters/3ds Max/Max2Babylon/Exporter/GLTFBufferService.cs

@@ -76,6 +76,27 @@ namespace Max2Babylon
             return gltf.bufferViewFloatVec4;
         }
 
+        public GLTFBufferView GetBufferViewFloatMat4(GLTF gltf, GLTFBuffer buffer)
+        {
+            if (gltf.bufferViewFloatMat4 == null)
+            {
+                var bufferView = CreateBufferView(gltf, buffer, "bufferViewFloatMat4");
+                gltf.bufferViewFloatMat4 = bufferView;
+            }
+            return gltf.bufferViewFloatMat4;
+        }
+
+        public GLTFBufferView GetBufferViewUnsignedShortVec4(GLTF gltf, GLTFBuffer buffer)
+        {
+            if (gltf.bufferViewUnsignedShortVec4 == null)
+            {
+                var bufferView = CreateBufferView(gltf, buffer, "bufferViewUnsignedShortVec4");
+                bufferView.byteStride = 8;
+                gltf.bufferViewUnsignedShortVec4 = bufferView;
+            }
+            return gltf.bufferViewUnsignedShortVec4;
+        }
+
         public GLTFBufferView GetBufferViewAnimationFloatScalar(GLTF gltf, GLTFBuffer buffer)
         {
             if (gltf.bufferViewAnimationFloatScalar == null)

+ 2 - 0
Exporters/3ds Max/Max2Babylon/Exporter/GLTFGlobalVertex.cs

@@ -9,5 +9,7 @@ namespace Max2Babylon
         public IPoint2 UV { get; set; }
         public IPoint2 UV2 { get; set; }
         public float[] Color { get; set; }
+        public ushort[] BonesIndices { get; set; }
+        public float[] BonesWeights { get; set; }
     }
 }

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

@@ -16,7 +16,7 @@ namespace Max2Babylon
         {
             Tools.UpdateVector3Control(gravityControl, Loader.Core.RootNode, "babylonjs_gravity");
             Tools.UpdateCheckBox(chkQuaternions, Loader.Core.RootNode, "babylonjs_exportquaternions");
-            Tools.UpdateCheckBox(chkAnimations, Loader.Core.RootNode, "babylonjs_exportnonoptimizedanimations");
+            Tools.UpdateCheckBox(chkAnimations, Loader.Core.RootNode, "babylonjs_donotoptimizeanimations");
 
             Tools.UpdateCheckBox(chkAutoPlay, Loader.Core.RootNode, "babylonjs_sound_autoplay");
             Tools.UpdateCheckBox(chkLoop, Loader.Core.RootNode, "babylonjs_sound_loop");
@@ -29,7 +29,7 @@ namespace Max2Babylon
         {
             Tools.PrepareVector3Control(gravityControl, Loader.Core.RootNode, "babylonjs_gravity", 0, -0.9f);
             Tools.PrepareCheckBox(chkQuaternions, Loader.Core.RootNode, "babylonjs_exportquaternions", 1);
-            Tools.PrepareCheckBox(chkAnimations, Loader.Core.RootNode, "babylonjs_exportnonoptimizedanimations", 0);
+            Tools.PrepareCheckBox(chkAnimations, Loader.Core.RootNode, "babylonjs_donotoptimizeanimations", 0);
 
             Tools.PrepareCheckBox(chkAutoPlay, Loader.Core.RootNode, "babylonjs_sound_autoplay", 1);
             Tools.PrepareCheckBox(chkLoop, Loader.Core.RootNode, "babylonjs_sound_loop", 1);

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

@@ -13,12 +13,45 @@ namespace Max2Babylon
 {
     public static class Tools
     {
+        // -------------------------
+        // --------- Math ----------
+        // -------------------------
+
         public static float Lerp(float min, float max, float t)
         {
             return min + (max - min) * t;
         }
 
         // -------------------------
+        // --------- Array ----------
+        // -------------------------
+
+        public static T[] SubArray<T>(T[] array, int startIndex, int count)
+        {
+            var result = new T[count];
+            for (int i = 0; i < count; i++)
+            {
+                result[i] = array[startIndex + i];
+            }
+            return result;
+        }
+
+        public static string ToString<T>(this T[] array)
+        {
+            var res = "[";
+            if (array.Length > 0)
+            {
+                res += array[0];
+                for (int i = 1; i < array.Length; i++)
+                {
+                    res += ", " + array[i];
+                }
+            }
+            res += "]";
+            return res;
+        }
+
+        // -------------------------
         // -- IIPropertyContainer --
         // -------------------------
 
@@ -268,6 +301,24 @@ namespace Max2Babylon
             return !value.Where((t, i) => Math.Abs(t - other[i]) > Epsilon).Any();
         }
 
+        public static IPoint2 CreateIPoint2FromArray(float[] array, int index)
+        {
+            var startIndex = index * 2;
+            return Loader.Global.Point2.Create(array[startIndex], array[startIndex + 1]);
+        }
+
+        public static IPoint3 CreateIPoint3FromArray(float[] array, int index)
+        {
+            var startIndex = index * 3;
+            return Loader.Global.Point3.Create(array[startIndex], array[startIndex + 1], array[startIndex + 2]);
+        }
+
+        public static IPoint4 CreateIPoint4FromArray(float[] array, int index)
+        {
+            var startIndex = index * 4;
+            return Loader.Global.Point4.Create(array[startIndex], array[startIndex + 1], array[startIndex + 2], array[startIndex + 3]);
+        }
+
         public static float[] ToArray(this IMatrix3 value)
         {
 

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/2CylinderEngine/2CylinderEngine.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/2CylinderEngine/2CylinderEngine.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/2CylinderEngine/2CylinderEngine.gltf


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/AnimatedMorphCube/AnimatedMorphCube.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/AnimatedMorphCube/AnimatedMorphCube.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/AnimatedMorphCube/AnimatedMorphCube.gltf


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/Buggy/Buggy.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/Buggy/Buggy.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/Buggy/Buggy.gltf


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/CartoonHouses.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/CartoonHouses.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/CartoonHouses.gltf


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/arbre.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/arbre_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/bois_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/cardboard_04_from_radu_luchian_dot_com.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/cloture.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/cloture_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/entree.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/entree_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison2.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison2_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison3.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison3_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/maison_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/sol.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/CartoonHouses/sol_baseColor.jpg


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/Instances/Instances.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/Instances/Instances.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/Instances/Instances.gltf


binární
Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/Material #37_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/Material #40_metallicRoughness.jpg


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/PBR_Textures.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/PBR_Textures.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/PBR_Textures.gltf


binární
Exporters/3ds Max/Samples/glTF 2.0/PBR_Textures/bump.jpg


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/RiggedElf/RiggedElf.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/RiggedElf/RiggedElf.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/RiggedElf/RiggedElf.gltf


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/SimpleTriangle/SimpleTriangle.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/SimpleTriangle/SimpleTriangle.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/SimpleTriangle/SimpleTriangle.gltf


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace.gltf


binární
Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace_texture_0001.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/SmilingFace_texture_0002.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/m0smiling_face_DF_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/SmilingFace/m0smiling_face_DF_metallicRoughness.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/Material #39_baseColor.jpg


binární
Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/Material #39_metallicRoughness.jpg


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle.babylon


binární
Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle.bin


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle.gltf


binární
Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle_Emissive.png


binární
Exporters/3ds Max/Samples/glTF 2.0/WaterBottle/WaterBottle_Normal.png


binární
Exporters/3ds Max/Samples/sourceModels/2CylinderEngine/2CylinderEngine.FBX


binární
Exporters/3ds Max/Samples/sourceModels/AnimatedMorphCube/AnimatedMorphCube.FBX


binární
Exporters/3ds Max/Samples/sourceModels/Buggy/Buggy.FBX


binární
Exporters/3ds Max/Samples/sourceModels/CartoonHouses/CartoonHouses.FBX


binární
Exporters/3ds Max/Samples/sourceModels/Instances/Instances.FBX


binární
Exporters/3ds Max/Samples/sourceModels/PBR_Textures_MetallicRoughness/PBR_Textures.FBX


binární
Exporters/3ds Max/Samples/sourceModels/RiggedElf/RiggedElf.FBX


binární
Exporters/3ds Max/Samples/sourceModels/SimpleTriangle/SimpleTriangle.FBX


binární
Exporters/3ds Max/Samples/sourceModels/SmilingFace/SmilingFace.FBX


binární
Exporters/3ds Max/Samples/sourceModels/WaterBottle/WaterBottle.FBX