Explorar o código

Export PBR materials from 3ds Max to .babylon file format using PBRMetallicRoughnessMaterial

noalak %!s(int64=8) %!d(string=hai) anos
pai
achega
bce22df90b

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

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

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

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

+ 13 - 17
Exporters/3ds Max/Max2Babylon/Exporter/BabylonExporter.GLTFExporter.Material.cs

@@ -147,39 +147,35 @@ namespace Max2Babylon
 
                 if (babylonStandardMaterial.diffuseTexture != null)
                 {
-                    Func<string, Bitmap> loadTexture = delegate (string textureName)
+                    Func<string, Bitmap> loadTextureFromOutput = delegate (string textureName)
                     {
-                        var pathDiffuse = Path.Combine(gltf.OutputPath, textureName);
-                        if (File.Exists(pathDiffuse))
-                        {
-                            return new Bitmap(pathDiffuse);
-                        }
-                        else
-                        {
-                            RaiseWarning(string.Format("GLTFExporter.Material | Texture {0} not found.", textureName), 2);
-                            return null;
-                        }
+                        return LoadTexture(Path.Combine(gltf.OutputPath, textureName));
                     };
 
-                    Bitmap diffuseBitmap = loadTexture(babylonStandardMaterial.diffuseTexture.name);
+                    Bitmap diffuseBitmap = loadTextureFromOutput(babylonStandardMaterial.diffuseTexture.name);
 
                     if (diffuseBitmap != null)
                     {
                         Bitmap specularBitmap = null;
                         if (babylonStandardMaterial.specularTexture != null)
                         {
-                            specularBitmap = loadTexture(babylonStandardMaterial.specularTexture.name);
+                            specularBitmap = loadTextureFromOutput(babylonStandardMaterial.specularTexture.name);
                         }
 
                         Bitmap opacityBitmap = null;
                         if (babylonStandardMaterial.diffuseTexture.hasAlpha == false && babylonStandardMaterial.opacityTexture != null)
                         {
-                            opacityBitmap = loadTexture(babylonStandardMaterial.opacityTexture.name);
+                            opacityBitmap = loadTextureFromOutput(babylonStandardMaterial.opacityTexture.name);
                         }
 
-                        // Retreive dimension from diffuse map
-                        var width = diffuseBitmap.Width;
-                        var height = diffuseBitmap.Height;
+                        // Retreive dimensions
+                        int width = 0;
+                        int height = 0;
+                        var haveSameDimensions = _getMinimalBitmapDimensions(out width, out height, diffuseBitmap, specularBitmap, opacityBitmap);
+                        if (!haveSameDimensions)
+                        {
+                            RaiseWarning("Diffuse, specular and opacity maps should have same dimensions", 2);
+                        }
 
                         // Create base color and metallic+roughness maps
                         Bitmap baseColorBitmap = new Bitmap(width, height);

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

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

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

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

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

@@ -13,6 +13,52 @@ namespace Max2Babylon
 {
     public static class Tools
     {
+        // -------------------------
+        // -- IIPropertyContainer --
+        // -------------------------
+
+        public static string GetStringProperty(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            string value = "";
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(ref value, 0);
+            return value;
+        }
+
+        public static int GetIntProperty(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            int value = 0;
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(ref value, 0);
+            return value;
+        }
+
+        public static bool GetBoolProperty(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            return propertyContainer.GetIntProperty(indexProperty) == 1;
+        }
+
+        public static float GetFloatProperty(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            float value = 0.0f;
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(ref value, 0, true);
+            return value;
+        }
+
+        public static IPoint3 GetPoint3Property(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            IPoint3 value = Loader.Global.Point3.Create(0, 0, 0);
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(value, 0);
+            return value;
+        }
+
+        public static IPoint4 GetPoint4Property(this IIPropertyContainer propertyContainer, int indexProperty)
+        {
+            IPoint4 value = Loader.Global.Point4.Create(0, 0, 0, 0);
+            propertyContainer.GetProperty(indexProperty).GetPropertyValue(value, 0);
+            return value;
+        }
+
+        // -------------------------
+
         public static IntPtr GetNativeHandle(this INativeObject obj)
         {
 #if MAX2015 || MAX2016 || MAX2017