Newer
Older
Hierarchical-Task-Network-Unity-3D / Assets / Quixel / Scripts / BridgeImporter / MegascansImporter.cs
#if UNITY_EDITOR

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System;
using Newtonsoft.Json.Linq;

namespace Quixel
{
    /// <summary>
    /// Completely rewritten importer with lots of commenting... Should be quite a bit faster than the previous one too.
    /// For those looking to customize, please be aware that some of the methods in here do not use built in Unity API calls.
    /// Ajwad Imran - Technical Artist @ Quixel.
    /// Lee Devonald - (Former) Technical Artist @ Quixel.
    /// </summary>
    public class MegascansImporter : Editor
    {
        private bool plant = false;
        private string assetName;
        private string type;
        private string mapName;
        private string folderNamingConvention;

        private string path;
        private string activeLOD;
        private int dispType;
        private int texPack;
        private int lodFadeMode;
        private int shaderType;
        private bool setupCollision = false;

        private bool applyToSelection;
        private bool addAssetToScene;
        private bool importAllTextures;

        private string texPath;
        private string matPath;

        private Material finalMat;
        private Material billboardMat;

        private bool highPoly = false;
        private bool isAlembic = false;
        private bool hasBillboardLOD = false;
        private bool hasBillboardLODOnly = false;

        /// <summary>
        /// Takes an imported JSON object, and breaks it into relevant components and data.
        /// Then calls relevant functions for actual import of asset.
        /// </summary>
        /// <param name="objectList"></param>
        public string ImportMegascansAssets(JObject objectList)
        {
            var startTime = System.DateTime.Now;
            activeLOD = (string)objectList["activeLOD"];
            string minLOD = (string)objectList["minLOD"];
            assetName = (string)objectList["name"];
            type = (string)objectList["type"];

            isAlembic = false;
            plant = false;
            highPoly = false;
            hasBillboardLOD = false;
            hasBillboardLODOnly = false;
            mapName = "";
            folderNamingConvention = (string)objectList["folderNamingConvention"];

            //get mesh components from the current object.
            JArray meshComps = (JArray)objectList["meshList"];

            //run a check to see if we're using Unity 5 or below, and then if we're trying to import a high poly mesh. if so, let the user know we are aborting the import.
            if (meshComps.Count > 0)
            {
                isAlembic = Path.GetExtension((string)meshComps[0]["path"]) == ".abc";
            }

            hasBillboardLOD = MegascansMeshUtils.ContainsLowestLOD((JArray)objectList["lodList"], minLOD, activeLOD);

            if (type.ToLower().Contains("3dplant"))
            {
                plant = true;
                if (minLOD == activeLOD)
                {
                    hasBillboardLODOnly = true;
                }
            }

            try
            {
                LoadPreferences();
                shaderType = MegascansUtilities.GetShaderType();
                MegascansUtilities.CalculateNumberOfOperations(objectList, dispType, texPack, shaderType, hasBillboardLODOnly);
                path = ConstructPath(objectList);

                if (path == null || path == "")
                {
                    Debug.Log("Asset: " + (string)objectList["name"] + " already exist in the Project. Please delete/rename the existing folder and re-import this asset.");
                    AssetDatabase.Refresh();
                    return null;
                }
            }
            catch (Exception ex)
            {
                Debug.Log("Error setting import path.");
                Debug.Log(ex.ToString());
                MegascansUtilities.HideProgressBar();
            }

            try
            {
                //process textures
                ProcessTextures(objectList);
                if (finalMat == null && !(plant && hasBillboardLODOnly))
                {
                    Debug.Log("Could not import the textures and create the material.");
                    return null;
                }
                else
                {
                    if (type.ToLower().Contains("surface") && applyToSelection)
                    {
                        foreach (MeshRenderer render in MegascansUtilities.GetSelectedMeshRenderers())
                        {
                            render.material = finalMat;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.Log("Error importing textures.");
                Debug.Log(ex.ToString());
                MegascansUtilities.HideProgressBar();
            }

            //process meshes
            if (meshComps == null && !type.Contains("surface"))
            {
                Debug.LogError("No meshes found. Please double check your export settings.");
                Debug.Log("Import failed.");
                return null;
            }

            if (meshComps.Count > 0)
            {
                if (activeLOD == "high")
                {
                    //detect if we're trying to import a high poly mesh...
                    string msg = "You are about to import a high poly mesh. \nThese meshes are usually millions of polygons and can cause instability to your project. \nWould you like to proceed?";
                    highPoly = EditorUtility.DisplayDialog("WARNING!", msg, "Yes", "No");
                }
                try
                {
                    //process meshes and prefabs
                    PrefabData prefData = new PrefabData(path, assetName, folderNamingConvention, lodFadeMode, highPoly, addAssetToScene, setupCollision, hasBillboardLOD, isAlembic, false, false, finalMat, billboardMat, new List<string>(), new List<List<string>>());
                    MegascansMeshUtils.ProcessMeshes(objectList, path, highPoly, plant, prefData);
                }
                catch (Exception ex)
                {
                    Debug.Log("Error importing meshes.");
                    Debug.Log(ex.ToString());
                    MegascansUtilities.HideProgressBar();
                }
            }

            var endTime = System.DateTime.Now;
            var totalTime = endTime - startTime;
            Debug.Log("Asset Import Time: " + totalTime);
            AssetDatabase.Refresh();
            MegascansUtilities.HideProgressBar();
            Resources.UnloadUnusedAssets();
            GC.Collect();
            return path;
        }

        #region Texture Processing Methods

        void ProcessTextures(JObject objectList)
        {
            texPath = MegascansUtilities.ValidateFolderCreate(path, "Textures");
            matPath = Path.Combine(MegascansUtilities.ValidateFolderCreate(path, "Materials"), folderNamingConvention);

            if (!(plant && hasBillboardLODOnly))
            {
                MegascansUtilities.UpdateProgressBar(1.0f, "Processing Asset " + assetName, "Creating material...");
                finalMat = MegascansMaterialUtils.CreateMaterial(shaderType, matPath, isAlembic, dispType, texPack);
                ImportAllTextures(finalMat, (JArray)objectList["components"]);
                ImportAllTextures(finalMat, (JArray)objectList["packedTextures"]);
            }

            if (plant && hasBillboardLOD)
            {
                texPath = MegascansUtilities.ValidateFolderCreate(texPath, "Billboard");
                matPath += "_Billboard";
                MegascansUtilities.UpdateProgressBar(1.0f, "Processing Asset " + assetName, "Creating material...");
                billboardMat = MegascansMaterialUtils.CreateMaterial(shaderType, matPath, isAlembic, dispType, texPack);
                ImportAllTextures(billboardMat, (JArray)objectList["components-billboard"]);
                ImportAllTextures(billboardMat, (JArray)objectList["packed-billboard"]);
            }
        }

        void ImportAllTextures(Material mat, JArray texturesList)
        {
            try
            {
                List<string> typesOfTexturesAvailable = new List<string>();
                for (int i = 0; i < texturesList.Count; i++)
                {
                    typesOfTexturesAvailable.Add((string)texturesList[i]["type"]);
                }

                string destTexPath;
                Texture2D tex;

                for (int i = 0; i < texturesList.Count; i++)
                {
                    mapName = (string)texturesList[i]["type"];
                    MegascansUtilities.UpdateProgressBar(1.0f, "Processing Asset " + assetName, "Importing texture: " + mapName);

                    if ((string)texturesList[i]["type"] == "albedo" || ((string)texturesList[i]["type"] == "diffuse" && !typesOfTexturesAvailable.Contains("albedo")))
                    {
                        destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
                        MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath);
                        tex = texPrcsr.ImportTexture();

                        mat.SetTexture("_MainTex", tex);
                        mat.SetTexture("_BaseColorMap", tex);

                        if (shaderType == 1)
                        {
                            mat.SetTexture("_BaseMap", tex);
                            mat.SetColor("_BaseColor", Color.white);
                        }

                        if (MegascansUtilities.AlbedoHasOpacity((JObject)texturesList[i]["channelsData"]))
                        {
                            float alphaCutoff = 0.33f;
                            texPrcsr.AdjustAlphaCutoff();

                            if (shaderType > 0)
                            {
                                mat.SetFloat("_AlphaClip", 1);
                                mat.SetFloat("_Cutoff", 0.1f);
                                mat.SetFloat("_Mode", 1);
                                mat.SetFloat("_Cull", 0);
                                mat.EnableKeyword("_ALPHATEST_ON");
                            }
                            else
                            {
                                mat.SetInt("_AlphaCutoffEnable", 1);
                                mat.SetFloat("_AlphaCutoff", alphaCutoff);
                                mat.SetInt("_DoubleSidedEnable", 1);

                                mat.SetOverrideTag("RenderType", "TransparentCutout");
                                mat.SetInt("_ZTestGBuffer", (int)UnityEngine.Rendering.CompareFunction.Equal);
                                mat.SetInt("_CullMode", (int)UnityEngine.Rendering.CullMode.Off);
                                mat.SetInt("_CullModeForward", (int)UnityEngine.Rendering.CullMode.Back);
                                mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                                mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                                mat.SetInt("_ZWrite", 1);
                                mat.renderQueue = 2450;
                                mat.SetInt("_ZTestGBuffer", (int)UnityEngine.Rendering.CompareFunction.Equal);

                                mat.EnableKeyword("_ALPHATEST_ON");
                                mat.EnableKeyword("_DOUBLESIDED_ON");
                                mat.DisableKeyword("_BLENDMODE_ALPHA");
                            }
                        }
                    }
                    else if ((string)texturesList[i]["type"] == "specular")
                    {
                        if (texPack > 0)
                        {
                            destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
                            MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath);
                            tex = texPrcsr.ImportTexture();

                            mat.SetTexture("_SpecGlossMap", tex);
                            mat.SetTexture("_SpecularColorMap", tex);
                            mat.SetColor("_SpecColor", new UnityEngine.Color(1.0f, 1.0f, 1.0f));
                            mat.SetColor("_SpecularColor", new UnityEngine.Color(1.0f, 1.0f, 1.0f));
                            mat.SetFloat("_WorkflowMode", 0);
                            mat.SetFloat("_MaterialID", 4);
                            mat.EnableKeyword("_METALLICSPECGLOSSMAP");
                            mat.EnableKeyword("_SPECGLOSSMAP");
                            mat.EnableKeyword("_SPECULAR_SETUP");
                            mat.EnableKeyword("_SPECULARCOLORMAP");
                            mat.EnableKeyword("_MATERIAL_FEATURE_SPECULAR_COLOR");
                        }
                    }
                    else if ((string)texturesList[i]["type"] == "masks")
                    {
                        if (texPack < 1 || shaderType < 1)
                        {
                            destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
                            MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath, false, false);
                            tex = texPrcsr.ImportTexture();

                            mat.SetTexture("_MaskMap", tex);
                            mat.SetTexture("_MetallicGlossMap", tex);
                            mat.EnableKeyword("_MASKMAP");
                            mat.SetFloat("_MaterialID", 1);
                            mat.EnableKeyword("_METALLICSPECGLOSSMAP");
                            mat.EnableKeyword("_METALLICGLOSSMAP");

                            bool hasMetalness;
                            bool hasAO;
                            bool hasGloss;

                            MegascansUtilities.MaskMapComponents((JObject)texturesList[i]["channelsData"], out hasMetalness, out hasAO, out hasGloss);

                            if (!hasMetalness)
                            {
                                mat.SetFloat("_Metallic", 1.0f);
                            }

                            if (hasAO)
                            {
                                mat.SetTexture("_OcclusionMap", tex);
                                mat.EnableKeyword("_OCCLUSIONMAP");
                            }
                        }

                    }
                    else if ((string)texturesList[i]["type"] == "normal")
                    {
                        string normalMapPath = (string)texturesList[i]["path"];
                        if (activeLOD == "high" && !normalMapPath.Contains("NormalBump"))
                        {
                            for (int x = 0; x < 10; x++)
                            {
                                string n = normalMapPath.Replace("_LOD" + x.ToString(), "Bump");
                                if (File.Exists(n))
                                {
                                    normalMapPath = n;
                                    break;
                                }

                            }
                            if (normalMapPath.Contains("NormalBump"))
                                continue;
                        }

                        destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
                        MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor(normalMapPath, destTexPath, true, false);
                        tex = texPrcsr.ImportTexture();
                        mat.SetTexture("_BumpMap", tex);
                        mat.SetTexture("_NormalMap", tex);
                        mat.EnableKeyword("_NORMALMAP_TANGENT_SPACE");
                        mat.EnableKeyword("_NORMALMAP");
                    }
                    else if ((string)texturesList[i]["type"] == "ao" && texPack > 0)
                    {
                        destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
                        MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath, false, false);
                        tex = texPrcsr.ImportTexture();
                        mat.SetTexture("_OcclusionMap", tex);
                        mat.EnableKeyword("_OCCLUSIONMAP");
                    }
                    else if ((string)texturesList[i]["type"] == "displacement")
                    {
                        if (dispType > 0)
                        {
                            destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
                            MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath, false, false);
                            tex = texPrcsr.ImportTexture();
                            mat.SetTexture("_HeightMap", tex);
                            mat.SetTexture("_ParallaxMap", tex);
                            mat.EnableKeyword("_DISPLACEMENT_LOCK_TILING_SCALE");
                            if (shaderType == 0)
                                mat.EnableKeyword("_HEIGHTMAP");
                            if (dispType == 1)
                            {
                                mat.EnableKeyword("_VERTEX_DISPLACEMENT");
                                mat.EnableKeyword("_VERTEX_DISPLACEMENT_LOCK_OBJECT_SCALE");
                            }
                            else if (dispType == 2)
                            {
                                mat.EnableKeyword("_PARALLAXMAP");
                                mat.EnableKeyword("_PIXEL_DISPLACEMENT");
                                mat.EnableKeyword("_PIXEL_DISPLACEMENT_LOCK_OBJECT_SCALE");
                            }
                        }
                    }
                    else if ((string)texturesList[i]["type"] == "translucency")
                    {
                        destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
                        MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath);
                        tex = texPrcsr.ImportTexture();

                        mat.SetTexture("_SubsurfaceMaskMap", tex);
                        mat.EnableKeyword("_SUBSURFACE_MASK_MAP");
                        mat.SetInt("_DiffusionProfile", 1);
                        mat.SetFloat("_EnableSubsurfaceScattering", 1);
                        if (!typesOfTexturesAvailable.Contains("transmission"))
                        {
                            mat.SetTexture("_ThicknessMap", tex);
                            mat.EnableKeyword("_THICKNESSMAP");
                        }
                        if (plant)
                        {
                            mat.SetInt("_DiffusionProfile", 2);
                            mat.SetFloat("_CoatMask", 0.0f);
                            mat.SetInt("_EnableWind", 1);
                            mat.EnableKeyword("_VERTEX_WIND");
                        }
                        MegascansMaterialUtils.AddSSSSettings(mat, shaderType);
                    }
                    else if ((string)texturesList[i]["type"] == "transmission")
                    {
                        destTexPath = Path.Combine(texPath, (string)texturesList[i]["nameOverride"]);
                        MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor((string)texturesList[i]["path"], destTexPath, false, false);
                        tex = texPrcsr.ImportTexture();

                        mat.SetTexture("_ThicknessMap", tex);
                        mat.EnableKeyword("_THICKNESSMAP");
                        mat.SetInt("_DiffusionProfile", 2);
                        MegascansMaterialUtils.AddSSSSettings(mat, shaderType);

                    }
                    else if (importAllTextures)
                    {
                        mapName = (string)texturesList[i]["type"];
                        string mapPath = (string)texturesList[i]["path"];
                        string otherTexFolder = MegascansUtilities.ValidateFolderCreate(texPath, "Others");
                        destTexPath = Path.Combine(otherTexFolder, (string)texturesList[i]["nameOverride"]);
                        MegascansTextureProcessor texPrcsr = new MegascansTextureProcessor(mapPath, destTexPath);
                        tex = texPrcsr.ImportTexture();
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.Log("Exception::MegascansImporter::ImportAllTextures:: " + ex.ToString());
                MegascansUtilities.HideProgressBar();
            }
        }

        #endregion

        #region Formatting Utilities

        void LoadPreferences()
        {
            path = MegascansUtilities.FixPath(EditorPrefs.GetString("QuixelDefaultPath", "Quixel/Megascans/"));
            dispType = EditorPrefs.GetInt("QuixelDefaultDisplacement");
            texPack = EditorPrefs.GetInt("QuixelDefaultTexPacking");
            shaderType = EditorPrefs.GetInt("QuixelDefaultShader");
            lodFadeMode = EditorPrefs.GetInt("QuixelDefaultLodFadeMode", 1);
            setupCollision = EditorPrefs.GetBool("QuixelDefaultSetupCollision", true);
            applyToSelection = EditorPrefs.GetBool("QuixelDefaultApplyToSelection", false);
            addAssetToScene = EditorPrefs.GetBool("QuixelDefaultAddAssetToScene", false);
            importAllTextures = EditorPrefs.GetBool("QuixelDefaultImportAllTextures", false);
        }

        /// <summary>
        /// Returns the final directory for our asset, creating subfolders where necessary in the 'Assets' directory.
        /// </summary>
        string ConstructPath(JObject objectList)
        {
            /// Make sure path is "Assets/...." not "D:/Unity Projects/My Project/Assets/...." otherwise the AssetDatabase cannot write files to it.
            /// Lastly I also match the path with the Application DataPath in order to make sure this is the right path selected from the Bridge.

            AssetDatabase.Refresh();

            string defPath = "";
            bool addNextPathPart = false;

            if ((string)objectList["exportPath"] != "")
            {
                path = (string)objectList["exportPath"];
            }
            else
            {
                defPath = "Assets";
                addNextPathPart = true;
            }

            string[] pathParts = MegascansUtilities.FixSlashes(path).Split('/');

            List<string> finalPathParts = new List<string>();

            foreach (string part in pathParts)
            {
                if (part == "Assets" && !addNextPathPart)
                {
                    addNextPathPart = true;
                }

                if (addNextPathPart)
                {
                    finalPathParts.Add(part);
                }
            }

            if (!addNextPathPart)
            {
                return null;
            }

            //First, create the user specified path from the importer settings.

            if (finalPathParts.Count > 0)
            {
                for (int i = 0; i < finalPathParts.Count; i++)
                {
                    defPath = MegascansUtilities.ValidateFolderCreate(defPath, finalPathParts[i]); //FixSlashes(Path.Combine(defPath, finalPathParts[i]));//ValidateFolderCreate(defPath, finalPathParts[i]);
                }
            }

            if (!AssetDatabase.IsValidFolder(defPath))
            {
                return null;
            }

            //then create check to see if the asset type subfolder exists, create it if it doesn't.
            defPath = MegascansUtilities.ValidateFolderCreate(defPath, MegascansUtilities.GetAssetType((string)objectList["path"]));
            defPath = MegascansUtilities.ValidateFolderCreate(defPath, folderNamingConvention);
            return defPath;
        }
        #endregion
    }
}
#endif