From f79f8c92a977f35e0f54bca4e5549c6697eb3dcf Mon Sep 17 00:00:00 2001 From: David Rios Date: Sun, 7 Jul 2024 19:48:02 -0300 Subject: [PATCH 01/12] Separate import into steps and update material creation --- Editor/ADTUtility.cs | 128 +++++++++++ ...alGenerator.cs.meta => ADTUtility.cs.meta} | 2 +- Editor/AssetConversionManager.cs | 152 ++++++++++++- Editor/ItemCollectionUtility.cs | 106 ++++----- Editor/M2Utility.cs | 146 +++++------- Editor/MaterialUtility.cs | 210 ++++++++---------- Editor/TerrainMaterialGenerator.cs | 87 -------- Editor/WMOUtility.cs | 82 ++++++- Editor/WoWExportUnityPostProcessor.cs | 82 +++---- Editor/WoWUnityWindow.cs | 155 +++++++++++++ Editor/WoWUnityWindow.cs.meta | 11 + 11 files changed, 744 insertions(+), 417 deletions(-) create mode 100644 Editor/ADTUtility.cs rename Editor/{TerrainMaterialGenerator.cs.meta => ADTUtility.cs.meta} (83%) delete mode 100644 Editor/TerrainMaterialGenerator.cs create mode 100644 Editor/WoWUnityWindow.cs create mode 100644 Editor/WoWUnityWindow.cs.meta diff --git a/Editor/ADTUtility.cs b/Editor/ADTUtility.cs new file mode 100644 index 0000000..64355d3 --- /dev/null +++ b/Editor/ADTUtility.cs @@ -0,0 +1,128 @@ +using Newtonsoft.Json; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; + +namespace WowUnity +{ + class ADTUtility + { + public static bool IsAdtObj(string path) + { + return Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+\.obj$"); + } + + public static bool IsAdtAny(string path) + { + return Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+\.(prefab|obj)$"); + } + + public static void PostProcessImport(string path) + { + Debug.Log($"{path}: processing adt"); + + if (M2Utility.FindPrefab(path) != null) + { + return; + } + + var imported = AssetDatabase.LoadAssetAtPath(path); + + var dirName = Path.GetDirectoryName(path); + string mainDataPath = Application.dataPath.Replace("Assets", ""); + + Renderer[] renderers = imported.GetComponentsInChildren(); + AssetDatabase.StartAssetEditing(); + try + { + var total = (float)renderers.Count(); + + for (var i = 0; i < total; i++) { + var renderer = renderers[i]; + string pathToMetadata = $"{dirName}/tex_{renderer.name}.json"; + + EditorUtility.DisplayProgressBar("Creating terrain materials.", pathToMetadata, i / total); + + var sr = new StreamReader(mainDataPath + pathToMetadata); + var jsonData = sr.ReadToEnd(); + sr.Close(); + + var metadata = JsonConvert.DeserializeObject(jsonData); + if (metadata.layers.Count == 0) + { + continue; + } + + for (var idx = 0; idx < metadata.layers.Count; idx++) + { + var texture = metadata.layers[idx]; + texture.assetPath = Path.GetRelativePath(mainDataPath, Path.GetFullPath(Path.Join(dirName, texture.file))); + metadata.layers[idx] = texture; + } + + renderer.material = MaterialUtility.GetTerrainMaterial(dirName, renderer.name, metadata); + + //ADT Liquid Volume Queue + // LiquidUtility.QueueLiquidData($"{dirName}/liquid_{renderer.name}.json"); + } + + } catch (System.Exception) + { + Debug.LogError($"{path}: failed processing terrain"); + throw; + } + finally + { + AssetDatabase.StopAssetEditing(); + AssetDatabase.SaveAssets(); + EditorUtility.ClearProgressBar(); + } + + GameObject prefab = M2Utility.FindOrCreatePrefab(path); + + var rootDoodadSetsObj = new GameObject("EnvironmentSet") { isStatic = true }; + + GameObject prefabInst = (GameObject)PrefabUtility.InstantiatePrefab(prefab); + rootDoodadSetsObj.transform.parent = prefabInst.transform; + PrefabUtility.ApplyPrefabInstance(prefabInst, InteractionMode.AutomatedAction); + PrefabUtility.SavePrefabAsset(prefab); + Object.DestroyImmediate(rootDoodadSetsObj); + Object.DestroyImmediate(prefabInst); + AssetDatabase.Refresh(); + } + + public class Tex + { + public List layers; + } + + public class Layer + { + public uint index; + public uint effectID; + public float scale; + public uint fileDataID; + public string file; + public string assetPath; + } + + public class EffectModel + { + public int fileDataID; + public string fileName; + } + + public class Effect + { + public int ID; + public int Density; + public int Sound; + public List DoodadID; + public List DoodadWeight; + public Dictionary DoodadModelIDs; + } + } +} diff --git a/Editor/TerrainMaterialGenerator.cs.meta b/Editor/ADTUtility.cs.meta similarity index 83% rename from Editor/TerrainMaterialGenerator.cs.meta rename to Editor/ADTUtility.cs.meta index d2f88b4..cd08256 100644 --- a/Editor/TerrainMaterialGenerator.cs.meta +++ b/Editor/ADTUtility.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 3e9ae32f6d20954408daeb007feace08 +guid: ed7b6e837080a3c489dd2bb3ea763cb3 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/AssetConversionManager.cs b/Editor/AssetConversionManager.cs index d9fbaf5..65078cc 100644 --- a/Editor/AssetConversionManager.cs +++ b/Editor/AssetConversionManager.cs @@ -1,5 +1,7 @@ -using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; +using System.Linq; using UnityEditor; using UnityEngine; @@ -7,12 +9,152 @@ namespace WowUnity { class AssetConversionManager { - public static void ProcessAssets() + private static ConcurrentQueue importedModelPathQueue = new(); + private static bool isBusy = false; + + public static void QueuePostprocess(string filePath) + { + importedModelPathQueue.Enqueue(filePath); + } + + public static bool HasQueue() + { + return importedModelPathQueue.Count > 0; + } + + public static bool IsBusy() + { + return isBusy; + } + + public static void RunPostProcessImports() + { + var itemsToProcess = importedModelPathQueue.Count; + var itemsProcessed = 0f; + + List<(string, TextAsset)> hasPlacement = new(); + + while (importedModelPathQueue.TryDequeue(out string path)) + { + Debug.Log($"{path}: postprocessing"); + + if (EditorUtility.DisplayCancelableProgressBar("Postprocessing WoW assets", path, itemsProcessed / itemsToProcess)) + { + break; + } + + var dirName = Path.GetDirectoryName(path); + string pathToMetadata = dirName + "/" + Path.GetFileNameWithoutExtension(path) + ".json"; + string mainDataPath = Application.dataPath.Replace("Assets", ""); + + var sr = new StreamReader(mainDataPath + pathToMetadata); + var jsonData = sr.ReadToEnd(); + sr.Close(); + + M2Utility.PostProcessImport(path, jsonData); + WMOUtility.PostProcessImport(path, jsonData); + + SetupPhysics(path); + + TextAsset placementData = AssetDatabase.LoadAssetAtPath(path.Replace(".obj", "_ModelPlacementInformation.csv")); + if (placementData != null) + { + hasPlacement.Add((path, placementData)); + } + + itemsProcessed++; + } + + itemsToProcess = hasPlacement.Count; + itemsProcessed = 0f; + + foreach (var (path, placementData) in hasPlacement) + { + if (EditorUtility.DisplayCancelableProgressBar("Placing doodads", path, itemsProcessed / itemsToProcess)) + { + break; + } + Debug.Log($"{path}: placing models"); + ItemCollectionUtility.PlaceModels(M2Utility.FindPrefab(path), placementData); + itemsProcessed++; + } + } + + public static void SetupPhysics(string path) { - EditorApplication.update -= ProcessAssets; + GameObject physicsPrefab = AssetDatabase.LoadAssetAtPath(path.Replace(".obj", ".phys.obj")); + if (physicsPrefab == null) + { + return; + } + + var prefab = M2Utility.FindPrefab(path); + + if (prefab.transform.Find("Collision") != null) + { + return; + } + + var collisionMesh = physicsPrefab.GetComponentInChildren(); + if (collisionMesh == null) + { + return; + } + + var prefabInst = PrefabUtility.InstantiatePrefab(prefab) as GameObject; + prefabInst.GetComponentsInChildren().ToList().ForEach(collider => Object.DestroyImmediate(collider)); + + GameObject collider = new(); + collider.transform.SetParent(prefabInst.transform); + collider.name = "Collision"; + MeshCollider parentCollider = collider.AddComponent(); + parentCollider.sharedMesh = collisionMesh.sharedMesh; + PrefabUtility.ApplyPrefabInstance(prefabInst, InteractionMode.AutomatedAction); + PrefabUtility.SavePrefabAsset(prefab); - M2Utility.PostProcessImports(); - ItemCollectionUtility.BeginQueue(); + Object.DestroyImmediate(prefabInst); + } + + public static void PostProcessImports() + { + if (importedModelPathQueue.Count == 0) + { + return; + } + + isBusy = true; + + try + { + RunPostProcessImports(); + } finally + { + EditorUtility.ClearProgressBar(); + } + + Debug.Log("PostProcessImports done"); + isBusy = false; + } + + public static void JobPostprocessAllAssets() + { + importedModelPathQueue.Clear(); + EditorUtility.DisplayProgressBar("Postprocessing WoW assets", "Looking for assets.", 0); + try + { + string[] allAssets = AssetDatabase.GetAllAssetPaths(); + foreach (string path in allAssets) + { + if (WoWExportUnityPostprocessor.ValidAsset(path) && !ADTUtility.IsAdtObj(path)) + { + QueuePostprocess(path); + } + } + } finally + { + EditorUtility.ClearProgressBar(); + } + PostProcessImports(); } } } diff --git a/Editor/ItemCollectionUtility.cs b/Editor/ItemCollectionUtility.cs index fb75e48..7ed114e 100644 --- a/Editor/ItemCollectionUtility.cs +++ b/Editor/ItemCollectionUtility.cs @@ -1,5 +1,3 @@ -using System.Collections; -using System.Collections.Generic; using System.Globalization; using UnityEngine; using UnityEditor; @@ -18,15 +16,7 @@ public class ItemCollectionUtility public static readonly float MAP_SIZE = MAXIMUM_DISTANCE_FROM_ORIGIN * 2f; public static readonly float ADT_SIZE = MAP_SIZE / 64f; - private static List queuedPlacementInformationPaths = new List(); - private static List missingFilesInQueue = new List(); - - public static bool isADT(TextAsset modelPlacementInformation) - { - return Regex.IsMatch(modelPlacementInformation.name, @"adt_\d{2}_\d{2}"); - } - - public static void GenerateADT(GameObject prefab, TextAsset modelPlacementInformation) + public static void PlaceModels(GameObject prefab, TextAsset modelPlacementInformation) { GameObject instantiatedGameObject = (GameObject)PrefabUtility.InstantiatePrefab(prefab); instantiatedGameObject.isStatic = true; @@ -35,31 +25,45 @@ public static void GenerateADT(GameObject prefab, TextAsset modelPlacementInform childTransform.gameObject.isStatic = true; } - string path = AssetDatabase.GetAssetPath(prefab); - ParseFileAndSpawnDoodads(instantiatedGameObject, modelPlacementInformation); - string parentPath = AssetDatabase.GetAssetPath(prefab); - - if (Path.GetExtension(parentPath) == ".prefab") - { - PrefabUtility.ApplyPrefabInstance(instantiatedGameObject, InteractionMode.AutomatedAction); - PrefabUtility.SavePrefabAsset(prefab); - } - else - { - PrefabUtility.SaveAsPrefabAsset(instantiatedGameObject, parentPath.Replace(Path.GetExtension(parentPath), ".prefab")); - } + PrefabUtility.ApplyPrefabInstance(instantiatedGameObject, InteractionMode.AutomatedAction); + PrefabUtility.SavePrefabAsset(prefab); Object.DestroyImmediate(instantiatedGameObject); } private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, TextAsset modelPlacementInformation) { + var isAdt = Regex.IsMatch(modelPlacementInformation.name, @"adt_\d+_\d+"); + + Transform doodadSetRoot; + if (isAdt) { + doodadSetRoot = instantiatedPrefabGObj.transform.Find("EnvironmentSet"); + } else { + doodadSetRoot = instantiatedPrefabGObj.transform.Find("DoodadSets"); + } + + if (doodadSetRoot == null) + { + Debug.LogWarning("No doodad set root found in " + instantiatedPrefabGObj.name); + return; + } + + if (doodadSetRoot.Find("doodadsplaced") != null) + { + return; + } + string[] records = modelPlacementInformation.text.Split(CSV_LINE_SEPERATOR); foreach (string record in records.Skip(1)) { string[] fields = record.Split(CSV_COLUMN_SEPERATOR); + if (fields.Length < 11) + { + continue; + } + string doodadPath = Path.GetDirectoryName(PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(instantiatedPrefabGObj)) + Path.DirectorySeparatorChar + fields[0]; doodadPath = Path.GetFullPath(doodadPath); doodadPath = $"Assets{Path.DirectorySeparatorChar}" + doodadPath.Substring(Application.dataPath.Length + 1); //This is so nifty :3 @@ -68,9 +72,8 @@ private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, Quaternion doodadRotation = Quaternion.identity; float doodadScale = float.Parse(fields[8], CultureInfo.InvariantCulture); - if (isADT(modelPlacementInformation)) + if (isAdt) { - doodadPosition.x = MAXIMUM_DISTANCE_FROM_ORIGIN - float.Parse(fields[1], CultureInfo.InvariantCulture); doodadPosition.z = (MAXIMUM_DISTANCE_FROM_ORIGIN - float.Parse(fields[3], CultureInfo.InvariantCulture)) * -1f; doodadPosition.y = float.Parse(fields[2], CultureInfo.InvariantCulture); @@ -81,6 +84,15 @@ private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, eulerRotation.z = float.Parse(fields[4], CultureInfo.InvariantCulture) * -1; doodadRotation.eulerAngles = eulerRotation; + + var spawned = SpawnDoodad(doodadPath, doodadPosition, doodadRotation, doodadScale, doodadSetRoot); + var doodadSets = fields[13].Split(","); + foreach (var setName in doodadSets) { + var childObj = spawned.transform.Find($"DoodadSets/{setName}"); + if (childObj != null) { + childObj.gameObject.SetActive(true); + } + } } else { @@ -95,20 +107,24 @@ private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, float.Parse(fields[6], CultureInfo.InvariantCulture) * -1, float.Parse(fields[4], CultureInfo.InvariantCulture) * -1 ); - } - SpawnDoodad(doodadPath, doodadPosition, doodadRotation, doodadScale, instantiatedPrefabGObj.transform); + var doodadSubsetRoot = doodadSetRoot.transform.Find(fields[9]); + SpawnDoodad(doodadPath, doodadPosition, doodadRotation, doodadScale, doodadSubsetRoot); + } } + + var placed = new GameObject("doodadsplaced"); + placed.transform.parent = doodadSetRoot.transform; } - private static void SpawnDoodad(string path, Vector3 position, Quaternion rotation, float scaleFactor, Transform parent) + private static GameObject SpawnDoodad(string path, Vector3 position, Quaternion rotation, float scaleFactor, Transform parent) { GameObject exisitingPrefab = M2Utility.FindOrCreatePrefab(path); if (exisitingPrefab == null) { Debug.LogWarning("Object was not spawned because it could not be found: " + path); - return; + return null; } GameObject newDoodadInstance = PrefabUtility.InstantiatePrefab(exisitingPrefab, parent) as GameObject; @@ -116,36 +132,8 @@ private static void SpawnDoodad(string path, Vector3 position, Quaternion rotati newDoodadInstance.transform.localPosition = position; newDoodadInstance.transform.localRotation = rotation; newDoodadInstance.transform.localScale = new Vector3(scaleFactor, scaleFactor, scaleFactor); - } - - public static void QueuePlacementData(string filePath) - { - queuedPlacementInformationPaths.Add(filePath); - } - public static void BeginQueue() - { - if (queuedPlacementInformationPaths.Count == 0) - { - return; - } - - List iteratingList = new List(queuedPlacementInformationPaths); - - foreach (string path in iteratingList) - { - TextAsset placementData = AssetDatabase.LoadAssetAtPath(path); - string prefabPath = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + Path.GetFileName(path).Replace("_ModelPlacementInformation.csv", ".obj"); - GameObject prefab = AssetDatabase.LoadAssetAtPath(prefabPath); - GenerateADT(prefab, placementData); - - queuedPlacementInformationPaths.Remove(path); - } - - foreach (string missingFilePath in missingFilesInQueue) - { - Debug.Log("Warning, import could not be found: " + missingFilePath); - } + return newDoodadInstance; } } } diff --git a/Editor/M2Utility.cs b/Editor/M2Utility.cs index 7e542aa..4934423 100644 --- a/Editor/M2Utility.cs +++ b/Editor/M2Utility.cs @@ -2,9 +2,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using UnityEditor; using UnityEngine; @@ -12,49 +9,52 @@ namespace WowUnity { class M2Utility { - private static List importedModelPathQueue = new List(); - - public static void QueueMetadata(string filePath) + public static void PostProcessImport(string path, string jsonData) { - importedModelPathQueue.Add(filePath); - } + var metadata = JsonConvert.DeserializeObject(jsonData); + if (metadata.fileType != "m2") { + return; + } - public static void PostProcessImports() - { - if (importedModelPathQueue.Count == 0) + Debug.Log($"{path}: processing m2"); + + if (FindPrefab(path) != null) { return; } - List iteratingList = new List(importedModelPathQueue); + ProcessTextures(metadata.textures, Path.GetDirectoryName(path)); - foreach (string path in iteratingList) - { - M2 metadata = ReadMetadataFor(path); + var imported = AssetDatabase.LoadAssetAtPath(path); + + Renderer[] renderers = imported.GetComponentsInChildren(); + + var skinMaterials = MaterialUtility.GetSkinMaterials(metadata); - if (metadata == null) - continue; + // Configure materials + for (uint rendererIndex = 0; rendererIndex < renderers.Length; rendererIndex++) + { + var renderer = renderers[rendererIndex]; + var (material, isMatDoubleSided) = skinMaterials[rendererIndex]; + renderer.material = material; + } + AssetDatabase.Refresh(); - GameObject prefab = FindOrCreatePrefab(path); + GeneratePrefab(path); - if (metadata.textureTransforms.Count > 0 && metadata.textureTransforms[0].translation.timestamps.Count > 0) + if (metadata.textureTransforms.Count > 0 && metadata.textureTransforms[0].translation.timestamps.Count > 0) + { + for (int i = 0; i < metadata.textureTransforms.Count; i++) { - for (int i = 0; i < metadata.textureTransforms.Count; i++) - { - AnimationClip newClip = AnimationUtility.CreateAnimationClip(metadata.textureTransforms[i]); - AssetDatabase.CreateAsset(newClip, Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + "[" + i + "]" + ".anim"); - } + AnimationClip newClip = AnimationUtility.CreateAnimationClip(metadata.textureTransforms[i]); + AssetDatabase.CreateAsset(newClip, Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + "[" + i + "]" + ".anim"); } } - - //Processing done: remove all paths from the queue - importedModelPathQueue.Clear(); } public static GameObject FindOrCreatePrefab(string path) { - string prefabPath = Path.ChangeExtension(path, "prefab"); - GameObject existingPrefab = AssetDatabase.LoadAssetAtPath(prefabPath); + GameObject existingPrefab = FindPrefab(path); if (existingPrefab == null) { @@ -64,6 +64,12 @@ public static GameObject FindOrCreatePrefab(string path) return existingPrefab; } + public static GameObject FindPrefab(string path) + { + string prefabPath = Path.ChangeExtension(path, "prefab"); + return AssetDatabase.LoadAssetAtPath(prefabPath); + } + public static GameObject GeneratePrefab(string path) { string prefabPath = Path.ChangeExtension(path, "prefab"); @@ -75,12 +81,8 @@ public static GameObject GeneratePrefab(string path) return null; } - ConfigureRendererMaterials(importedModelObject); - - ModelImporter modelImporter = ModelImporter.GetAtPath(path) as ModelImporter; - modelImporter.SearchAndRemapMaterials(ModelImporterMaterialName.BasedOnMaterialName, ModelImporterMaterialSearch.RecursiveUp); - - GameObject rootModelInstance = PrefabUtility.InstantiatePrefab(importedModelObject) as GameObject; + var rootObj = new GameObject() { isStatic = true }; + GameObject rootModelInstance = PrefabUtility.InstantiatePrefab(importedModelObject, rootObj.transform) as GameObject; //Set the object as static, and all it's child objects rootModelInstance.isStatic = true; @@ -89,77 +91,27 @@ public static GameObject GeneratePrefab(string path) childTransform.gameObject.isStatic = true; } - GameObject newPrefab = PrefabUtility.SaveAsPrefabAssetAndConnect(rootModelInstance, prefabPath, InteractionMode.AutomatedAction); + GameObject newPrefab = PrefabUtility.SaveAsPrefabAssetAndConnect(rootObj, prefabPath, InteractionMode.AutomatedAction); AssetDatabase.Refresh(); - UnityEngine.Object.DestroyImmediate(rootModelInstance); + UnityEngine.Object.DestroyImmediate(rootObj); return newPrefab; } - private static void ConfigureRendererMaterials(GameObject importedModelObject) - { - //Manage materials for imported models. + public static void ProcessTextures(List textures, string dirName) { + string mainDataPath = Application.dataPath.Replace("Assets", ""); - //First, we need to sample all renderers that belong to the specified game object. - Renderer[] renderers = importedModelObject.GetComponentsInChildren(); - - //Now we will loop through all renderers present in the game object - //and call the MaterialUtility to create the asset. - for (int rendererIndex = 0; rendererIndex < renderers.Length; rendererIndex++) - { - for (int materialIndex = 0; materialIndex < renderers[rendererIndex].sharedMaterials.Length; materialIndex++) - { - //We don't need to worry about repeat materials here, - //because the CreateMaterialAsset already handles this case for us. - MaterialUtility.ExtractMaterialFromAsset(renderers[rendererIndex].sharedMaterials[materialIndex]); - } + for (var idx = 0; idx < textures.Count; idx++) { + var texture = textures[idx]; + texture.assetPath = Path.GetRelativePath(mainDataPath, Path.GetFullPath(Path.Join(dirName, texture.fileNameExternal))); + textures[idx] = texture; } - AssetDatabase.Refresh(); - } - - public static M2 ReadMetadataFor(string path) - { - string pathToMetadata = Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + ".json"; - - if (!File.Exists(pathToMetadata)) - { - return null; - } - - var sr = new StreamReader(Application.dataPath.Replace("Assets", "") + pathToMetadata); - var fileContents = sr.ReadToEnd(); - sr.Close(); - - return JsonConvert.DeserializeObject(fileContents); - } - - public static Material GetMaterialData(string materialName, M2 metadata) - { - Material data = new Material(); - data.flags = 0; - data.blendingMode = 0; - - //I have no idea why they sometimes don't match sizes. - // I'm guessing if there's no material entry, default is intended. - for(int i = 0; i < metadata.textures.Count; i++) - { - Texture texture = metadata.textures[i]; - if (texture.mtlName != materialName) - continue; - - if (metadata.materials.Count <= i) - i = metadata.materials.Count - 1; - - data = metadata.materials[i]; - break; - } - - return data; } [Serializable] public class M2 { + public string fileType; public uint fileDataID; public string fileName; public string internalName; @@ -189,9 +141,11 @@ public struct SubMesh [Serializable] public struct TextureUnit { - public uint skinSelectionIndex; + public uint skinSectionIndex; public uint geosetIndex; + public uint materialIndex; public uint colorIndex; + public uint textureComboIndex; } [Serializable] @@ -199,7 +153,9 @@ public struct Texture { public string fileNameInternal; public string fileNameExternal; + public string assetPath; public string mtlName; + public string uniqMtlName; public short flag; public uint fileDataID; } diff --git a/Editor/MaterialUtility.cs b/Editor/MaterialUtility.cs index e47f2e0..1c537e3 100644 --- a/Editor/MaterialUtility.cs +++ b/Editor/MaterialUtility.cs @@ -1,19 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using UnityEditor; -using UnityEditor.AssetImporters; using UnityEngine; namespace WowUnity { class MaterialUtility { - public const string LIT_SHADER = "Universal Render Pipeline/Simple Lit"; + public const string LIT_SHADER = "Universal Render Pipeline/Lit"; public const string UNLIT_SHADER = "Universal Render Pipeline/Unlit"; public const string EFFECT_SHADER = "Universal Render Pipeline/Particles/Unlit"; public const string ADT_CHUNK_SHADER = "wow.unity/TerrainChunk"; @@ -38,55 +33,56 @@ public enum BlendModes : short BlendAdd = 7 } - public static Material ConfigureMaterial(MaterialDescription description, Material material, string modelImportPath, M2Utility.M2 metadata) + public enum MaterialFor: short { - if (Regex.IsMatch(Path.GetFileNameWithoutExtension(modelImportPath), @"adt_\d{2}_\d{2}")) - return ProcessADTMaterial(description, material, modelImportPath); + M2 = 0, + WMO = 1, + } - M2Utility.Material materialData = M2Utility.GetMaterialData(material.name, metadata); - Color materialColor = Color.white; - if (metadata != null && metadata.colors.Count > 0) - { - materialColor = ProcessMaterialColors(material, metadata); + public static Material GetMaterial(M2Utility.Texture texture, MaterialFor materialFor, short flags, uint blendingMode, int shader, Color materialColor) { + var colorName = materialColor == Color.white ? "W" : ColorUtility.ToHtmlStringRGBA(materialColor); + var matName = $"{Path.GetFileNameWithoutExtension(texture.fileNameExternal)}_TF{texture.flag}_F{flags:X}_B{blendingMode:X}_S{shader:X}_C{colorName}"; + var assetMatPath = Path.Join(Path.GetDirectoryName(texture.assetPath), $"{matName}.mat"); + + var material = AssetDatabase.LoadAssetAtPath(assetMatPath); + if (material != null) { + return material; } - - material.shader = Shader.Find(LIT_SHADER); - material.SetColor("_BaseColor", materialColor); - // Read a texture property from the material description. - TexturePropertyDescription textureProperty; - if (description.TryGetProperty("DiffuseColor", out textureProperty) && textureProperty.texture != null) - { - // Assign the texture to the material. - material.SetTexture("_MainTex", textureProperty.texture); + Debug.Log($"{matName}: material does not exist, creating."); + + material = new Material(Shader.Find(LIT_SHADER)); + material.SetFloat("_WorkflowMode", 0); + material.SetFloat("_Smoothness", 0); + if (shader == 1) { + material.SetFloat("_SmoothnessTextureChannel", 1); } - - ProcessFlagsForMaterial(material, materialData); - return material; - } - public static void ProcessFlagsForMaterial(Material material, M2Utility.Material data) - { //Flags first - if ((data.flags & (short)MaterialFlags.Unlit) != (short)MaterialFlags.None) + if ((flags & (short)MaterialFlags.Unlit) != (short)MaterialFlags.None) { material.shader = Shader.Find(UNLIT_SHADER); + material.SetFloat("_Cull", 0); } - if ((data.flags & (short)MaterialFlags.TwoSided) != (short)MaterialFlags.None) + Texture assetTexture = AssetDatabase.LoadAssetAtPath(texture.assetPath); + material.SetTexture("_BaseMap", assetTexture); + material.SetColor("_BaseColor", materialColor); + + if ((flags & (short)MaterialFlags.TwoSided) != (short)MaterialFlags.None) { material.doubleSidedGI = true; material.SetFloat("_Cull", 0); } //Now blend modes - if (data.blendingMode == (short)BlendModes.AlphaKey) + if (blendingMode == (short)BlendModes.AlphaKey) { material.EnableKeyword("_ALPHATEST_ON"); material.SetFloat("_AlphaClip", 1); } - if (data.blendingMode == (short)BlendModes.Alpha) + if (blendingMode == (short)BlendModes.Alpha) { material.SetOverrideTag("RenderType", "Transparent"); material.SetFloat("_Blend", 0); @@ -94,125 +90,115 @@ public static void ProcessFlagsForMaterial(Material material, M2Utility.Material material.SetFloat("_ZWrite", 0); } - if (data.blendingMode == (short)BlendModes.Add) + if (blendingMode == (short)BlendModes.Add) { material.SetOverrideTag("RenderType", "Transparent"); material.DisableKeyword("_ALPHAPREMULTIPLY_ON"); material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent; material.SetFloat("_Cutoff", 0); - material.SetFloat("_Blend", 1); + material.SetFloat("_Blend", 2); material.SetFloat("_Surface", 1); material.SetFloat("_SrcBlend", 1); material.SetFloat("_DstBlend", 1); material.SetFloat("_ZWrite", 0); material.SetShaderPassEnabled("ShadowCaster", false); } - } - - public static Color ProcessMaterialColors(Material material, M2Utility.M2 metadata) - { - int i, j, k; - Color newColor = Color.white; - if (metadata.skin == null || metadata.skin.textureUnits.Count <= 0) - { - return newColor; - } - for (i = 0; i < metadata.textures.Count; i++) + if (materialFor == MaterialFor.WMO && (flags & 16) == 16) { - if (material.name == metadata.textures[i].mtlName) - break; + material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive; + material.SetColor("_EmissionColor", Color.white); + material.SetTexture("_EmissionMap", assetTexture); } - for (j = 0; j < metadata.skin.textureUnits.Count; j++) - { - if (metadata.skin.textureUnits[j].geosetIndex == i) - break; - } + AssetDatabase.CreateAsset(material, assetMatPath); + AssetDatabase.SaveAssets(); - if (j < metadata.skin.textureUnits.Count) - k = (int)metadata.skin.textureUnits[j].colorIndex; - else - return newColor; - - if (k < metadata.colors.Count) - { - newColor.r = metadata.colors[k].color.values[0][0][0]; - newColor.g = metadata.colors[k].color.values[0][0][1]; - newColor.b = metadata.colors[k].color.values[0][0][2]; - newColor.a = 1; - } - - return newColor; + return material; } - public static Material ProcessADTMaterial(MaterialDescription description, Material material, string modelImportPath) + public static Dictionary GetSkinMaterials(M2Utility.M2 metadata) { - material.shader = Shader.Find(ADT_CHUNK_SHADER); + Dictionary mats = new(); - TexturePropertyDescription textureProperty; - if (description.TryGetProperty("DiffuseColor", out textureProperty) && textureProperty.texture != null) - { - material.SetTexture("_BaseMap", textureProperty.texture); - } + foreach (var textureUnit in metadata.skin.textureUnits) { + var texture = metadata.textures[metadata.textureCombos[checked((int)textureUnit.textureComboIndex)]]; + var unitMat = metadata.materials[checked((int)textureUnit.materialIndex)]; - LoadMetadataAndConfigureADT(material, modelImportPath); + var materialColor = Color.white; + Debug.Log($"color: {textureUnit.colorIndex}, {textureUnit.colorIndex != 0xffff}"); + if (textureUnit.colorIndex < 0xffff) + { + var colorValue = metadata.colors[(int)textureUnit.colorIndex].color.values[0][0]; + materialColor = new Color() { r = colorValue[0], g = colorValue[1], b = colorValue[2], a = 1 }; + } - return material; + mats[textureUnit.skinSectionIndex] = ( + GetMaterial(texture, MaterialFor.M2, unitMat.flags, unitMat.blendingMode, 0, materialColor), + (unitMat.flags & (short)MaterialFlags.TwoSided) != (short)MaterialFlags.None); // is two-sided + } + + return mats; } - public static void LoadMetadataAndConfigureADT(Material mat, string assetPath) + public static Dictionary GetWMOMaterials(WMOUtility.WMO metadata) { - string jsonFilePath = Path.GetDirectoryName(assetPath) + Path.DirectorySeparatorChar + mat.name + ".json"; - var sr = new StreamReader(Application.dataPath.Replace("Assets", "") + jsonFilePath); - var fileContents = sr.ReadToEnd(); - sr.Close(); + Dictionary mats = new(); - TerrainMaterialGenerator.Chunk newChunk = JsonUtility.FromJson(fileContents); + var texturesById = metadata.textures.ToDictionary((item) => item.fileDataID); - Vector4 scaleVector = new Vector4(); - TerrainMaterialGenerator.Layer currentLayer; - for (int i = 0; i < newChunk.layers.Count; i++) - { - currentLayer = newChunk.layers[i]; - string texturePath = Path.Combine(Path.GetDirectoryName(@assetPath), @currentLayer.file); - texturePath = Path.GetFullPath(texturePath); - texturePath = texturePath.Substring(texturePath.IndexOf($"Assets{Path.DirectorySeparatorChar}")); + foreach (var group in metadata.groups) { + for (var batchIdx = 0; batchIdx < group.renderBatches.Count; batchIdx++) { + var batch = group.renderBatches[batchIdx]; + var batchMat = metadata.materials[checked((int)batch.materialID)]; + var texture = texturesById[batchMat.texture1]; - Texture2D layerTexture = (Texture2D)AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D)); - mat.SetTexture("Layer_" + i, layerTexture); - scaleVector[i] = currentLayer.scale; + var material = GetMaterial(texture, MaterialFor.WMO, batchMat.flags, batchMat.blendMode, batchMat.shader, Color.white); + mats[$"{group.groupName}{batchIdx}"] = material; + } } - mat.SetVector("Scale", scaleVector); + return mats; } - public static void ExtractMaterialFromAsset(Material material) + public static Material GetTerrainMaterial(string dirName, string chunkName, ADTUtility.Tex metadata) { - string assetPath = AssetDatabase.GetAssetPath(material); - string newMaterialPath = "Assets/Materials/" + material.name + ".mat"; - Material newMaterialAsset; - - if (!Directory.Exists("Assets/Materials")) + var matDir = Path.Join(dirName, "terrain_materials"); + if (!Directory.Exists(matDir)) { - Directory.CreateDirectory("Assets/Materials"); + Directory.CreateDirectory(matDir); } - - if (!File.Exists(newMaterialPath)) + var assetMatPath = Path.Join(matDir, $"tex_{chunkName}.mat"); + + var assetMat = AssetDatabase.LoadAssetAtPath(assetMatPath); + if (assetMat != null) { - newMaterialAsset = new Material(material); - AssetDatabase.CreateAsset(newMaterialAsset, newMaterialPath); + return assetMat; } - else + + Debug.Log($"{assetMatPath}: material does not exist, creating."); + + var textures = metadata.layers.Select((item) => AssetDatabase.LoadAssetAtPath(item.assetPath)).ToList(); + var mask = AssetDatabase.LoadAssetAtPath(Path.Join(dirName, $"tex_{chunkName}.png")); + + assetMat = new Material(Shader.Find(ADT_CHUNK_SHADER)); + assetMat.SetTexture("_BaseMap", mask); + + Vector4 scaleVector = new Vector4(); + ADTUtility.Layer currentLayer; + for (int i = 0; i < metadata.layers.Count; i++) { - newMaterialAsset = AssetDatabase.LoadAssetAtPath(newMaterialPath); + currentLayer = metadata.layers[i]; + var layerTexture = AssetDatabase.LoadAssetAtPath(currentLayer.assetPath); + assetMat.SetTexture("Layer_" + i, layerTexture); + scaleVector[i] = currentLayer.scale; } - AssetImporter importer = AssetImporter.GetAtPath(assetPath); - importer.AddRemap(new AssetImporter.SourceAssetIdentifier(material), newMaterialAsset); + assetMat.SetVector("Scale", scaleVector); + + AssetDatabase.CreateAsset(assetMat, assetMatPath); - AssetDatabase.WriteImportSettingsIfDirty(assetPath); - AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); + return assetMat; } } } diff --git a/Editor/TerrainMaterialGenerator.cs b/Editor/TerrainMaterialGenerator.cs deleted file mode 100644 index e9fde3d..0000000 --- a/Editor/TerrainMaterialGenerator.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using UnityEditor; -using System.IO; -using System.Linq; -using System; -using System.Text.RegularExpressions; - -public class TerrainMaterialGenerator : EditorWindow -{ - public Shader terrainShader; - public List jsonFiles; - - [MenuItem("Window/Generate Terrain Material Data")] - public static void ShowWindow() - { - EditorWindow.GetWindow(typeof(TerrainMaterialGenerator)); - } - - void OnGUI() - { - GUILayout.Label("Generate Terrain Material Data", EditorStyles.boldLabel); - - // "target" can be any class derrived from ScriptableObject - // (could be EditorWindow, MonoBehaviour, etc) - ScriptableObject target = this; - SerializedObject so = new SerializedObject(target); - SerializedProperty terrainShaderProp = so.FindProperty("terrainShader"); - SerializedProperty jsonFileProp = so.FindProperty("jsonFiles"); - - EditorGUILayout.PropertyField(terrainShaderProp, true); // True means show children - EditorGUILayout.PropertyField(jsonFileProp, true); // True means show children - so.ApplyModifiedProperties(); // Remember to apply modified properties - - GUILayout.Space(5f); - - if (GUILayout.Button("Generate Data")) - { - ParseFile(); - } - } - - void ParseFile() - { - foreach (TextAsset chunkData in jsonFiles) - { - Chunk newChunk = JsonUtility.FromJson(chunkData.text); - - string filePath = AssetDatabase.GetAssetPath(chunkData); - Match match = Regex.Match(filePath, @"tex_\d{2}_\d{2}_\d{1,3}(?=\.json)"); - string materialName = match.Groups[0].Value; - - Material currentChunkMaterial = (Material)AssetDatabase.LoadAssetAtPath("Assets/Materials/" + materialName + ".mat", typeof(Material)); - currentChunkMaterial.shader = terrainShader; - - Vector4 scaleVector = new Vector4(); - Layer currentLayer; - for (int i = 0; i < newChunk.layers.Count; i++) - { - currentLayer = newChunk.layers[i]; - string texturePath = currentLayer.file.Replace("..\\..\\", "Assets\\world geometry\\"); - Debug.Log(texturePath); - Texture2D layerTexture = (Texture2D)AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D)); - currentChunkMaterial.SetTexture("Layer_" + i, layerTexture); - scaleVector[i] = currentLayer.scale; - } - - currentChunkMaterial.SetVector("Scale", scaleVector); - } - } - - [Serializable] - public struct Chunk - { - public List layers; - } - - [Serializable] - public struct Layer - { - public int index; - public int fileDataID; - public int scale; - public string file; - } -} diff --git a/Editor/WMOUtility.cs b/Editor/WMOUtility.cs index 707ca0a..d1bcf42 100644 --- a/Editor/WMOUtility.cs +++ b/Editor/WMOUtility.cs @@ -1,12 +1,62 @@ using Newtonsoft.Json; -using System.Collections; using System.Collections.Generic; +using System.IO; +using UnityEditor; using UnityEngine; namespace WowUnity { class WMOUtility { + public static void PostProcessImport(string path, string jsonData) + { + var metadata = JsonConvert.DeserializeObject(jsonData); + if (metadata.fileType != "wmo") { + return; + } + + Debug.Log($"{path}: processing wmo"); + + if (M2Utility.FindPrefab(path) != null) + { + return; + } + + M2Utility.ProcessTextures(metadata.textures, Path.GetDirectoryName(path)); + + var imported = AssetDatabase.LoadAssetAtPath(path); + + Renderer[] renderers = imported.GetComponentsInChildren(); + + var materials = MaterialUtility.GetWMOMaterials(metadata); + + for (uint rendererIndex = 0; rendererIndex < renderers.Length; rendererIndex++) + { + var renderer = renderers[rendererIndex]; + renderer.material = materials[renderer.name]; + } + AssetDatabase.Refresh(); + + GameObject prefab = M2Utility.FindOrCreatePrefab(path); + + if (metadata.doodadSets.Count > 0) { + var rootDoodadSetsObj = new GameObject("DoodadSets") { isStatic = true }; + foreach (var doodadSet in metadata.doodadSets) { + var setObj = new GameObject(doodadSet.name) { isStatic = true }; + setObj.transform.parent = rootDoodadSetsObj.transform; + setObj.SetActive(doodadSet.name != "Set_$DefaultGlobal"); + } + + GameObject prefabInst = (GameObject)PrefabUtility.InstantiatePrefab(prefab); + rootDoodadSetsObj.transform.parent = prefabInst.transform; + PrefabUtility.ApplyPrefabInstance(prefabInst, InteractionMode.AutomatedAction); + PrefabUtility.SavePrefabAsset(prefab); + Object.DestroyImmediate(rootDoodadSetsObj); + Object.DestroyImmediate(prefabInst); + AssetDatabase.Refresh(); + } + } + public static bool AssignVertexColors(WMOUtility.Group group, List gameObjects) { if (gameObjects.Count != group.renderBatches.Count) @@ -54,15 +104,18 @@ static Color[] GetVertexColorsInRange(WMOUtility.Group group, int start, int end public class WMO { + public string fileType; public uint fileDataID; public string fileName; public uint version; - public byte[] ambientColor; + public uint ambientColor; public uint areaTableID; - public BitArray flags; + public short flags; public List groups; public List groupNames; public List textures; + public List materials; + public List doodadSets; } public class Group @@ -79,8 +132,29 @@ public class RenderBatch { public ushort firstVertex; public ushort lastVertex; - public BitArray flags; + public short flags; public uint materialID; } + + public class Material { + public short flags; + public int shader; + public uint blendMode; + public uint texture1; + public uint color1; + public uint color1b; + public uint texture2; + public uint color2; + public uint groupType; + public uint texture3; + public uint color3; + public uint flags3; + } + + public class DoodadSet { + public string name; + public uint firstInstanceIndex; + public uint doodadCount; + } } } diff --git a/Editor/WoWExportUnityPostProcessor.cs b/Editor/WoWExportUnityPostProcessor.cs index 9313143..0ad7c8e 100644 --- a/Editor/WoWExportUnityPostProcessor.cs +++ b/Editor/WoWExportUnityPostProcessor.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; using UnityEditor; using UnityEngine; -using UnityEditor.AssetImporters; using System.IO; using System.Text.RegularExpressions; -using System.Linq; -using System; using WowUnity; public class WoWExportUnityPostprocessor : AssetPostprocessor @@ -20,14 +16,17 @@ public override uint GetVersion() return 1; } - static private bool ValidAsset(string path) + static public bool ValidAsset(string path) { - if (!path.Contains(".obj")) + if (!path.EndsWith(".obj")) return false; - if (path.Contains(".phys.obj")) + if (path.EndsWith(".phys.obj")) return false; - return true; + return ( + File.Exists(Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + ".json") || + Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+.obj$") + ); } public void OnPreprocessTexture() @@ -50,32 +49,26 @@ public void OnPreprocessTexture() public void OnPreprocessModel() { + ModelImporter modelImporter = assetImporter as ModelImporter; + if (!ValidAsset(assetPath)) { + if (assetPath.EndsWith(".phys.obj")) + { + modelImporter.bakeAxisConversion = true; + } return; } - ModelImporter modelImporter = assetImporter as ModelImporter; + Debug.Log($"{assetPath}: processing wow model"); + modelImporter.bakeAxisConversion = true; modelImporter.generateSecondaryUV = true; modelImporter.secondaryUVMarginMethod = ModelImporterSecondaryUVMarginMethod.Calculate; modelImporter.secondaryUVMinLightmapResolution = 16; modelImporter.secondaryUVMinObjectScale = 1; - modelImporter.materialImportMode = ModelImporterMaterialImportMode.ImportViaMaterialDescription; - modelImporter.materialName = ModelImporterMaterialName.BasedOnMaterialName; - modelImporter.materialSearch = ModelImporterMaterialSearch.RecursiveUp; - } - - public void OnPreprocessMaterialDescription(MaterialDescription description, Material material, AnimationClip[] materialAnimation) - { - if (!ValidAsset(assetPath)) - { - return; - } - - M2Utility.M2 metadata = M2Utility.ReadMetadataFor(assetPath); - material = MaterialUtility.ConfigureMaterial(description, material, assetPath, metadata); + modelImporter.materialImportMode = ModelImporterMaterialImportMode.None; } public void OnPostprocessModel(GameObject gameObject) @@ -85,54 +78,35 @@ public void OnPostprocessModel(GameObject gameObject) return; } - GameObject physicsPrefab = AssetDatabase.LoadAssetAtPath(assetPath.Replace(".obj", ".phys.obj")); - MeshRenderer[] childRenderers = gameObject.GetComponentsInChildren(); - - if (physicsPrefab == null || physicsPrefab.GetComponentInChildren() == null) + if (!File.Exists(assetPath.Replace(".obj", ".phys.obj"))) { + MeshRenderer[] childRenderers = gameObject.GetComponentsInChildren(); foreach (MeshRenderer child in childRenderers) { child.gameObject.AddComponent(); } } - else - { - GameObject collider = new GameObject(); - collider.transform.SetParent(gameObject.transform); - collider.name = "Collision"; - MeshFilter collisionMesh = physicsPrefab.GetComponentInChildren(); - MeshCollider parentCollider = collider.AddComponent(); - parentCollider.sharedMesh = collisionMesh.sharedMesh; - } } static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { - string path; - - for(int i = 0; i < importedAssets.Length; i++) + var hasWow = false; + foreach (string path in importedAssets) { - path = importedAssets[i]; - - //M2 Utility Queue if (ValidAsset(path)) { - M2Utility.QueueMetadata(path); - } - - //ADT/WMO Item Collection Queue - if (Path.GetFileName(path).Contains("_ModelPlacementInformation.csv")) - { - ItemCollectionUtility.QueuePlacementData(path); + AssetConversionManager.QueuePostprocess(path); + hasWow = true; } + } - //ADT Liquid Volume Queue - if (Regex.IsMatch(path, @"liquid_\d{2}_\d{2}(?=\.json)")) + if (hasWow && !AssetConversionManager.IsBusy()) + { + var processNow = EditorUtility.DisplayDialog("WoW assets imported", "There were WoW assets imported, they need to be processed to work properly. Do you want to process them now? They can also be processed later by opening menu bar Window > wow.unity and clicking Process under All Assets.", "Process now", "Do it later"); + if (processNow) { - LiquidUtility.QueueLiquidData(path); + AssetConversionManager.JobPostprocessAllAssets(); } } - - EditorApplication.update += AssetConversionManager.ProcessAssets; } } diff --git a/Editor/WoWUnityWindow.cs b/Editor/WoWUnityWindow.cs new file mode 100644 index 0000000..807d61c --- /dev/null +++ b/Editor/WoWUnityWindow.cs @@ -0,0 +1,155 @@ +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEngine; +using WowUnity; + +public class WoWUnityWindow : EditorWindow +{ + private Dictionary selectedAssets; + + [MenuItem("Window/wow.unity")] + public static void ShowWindow() + { + GetWindow("wow.unity"); + } + + private void OnEnable() + { + Selection.selectionChanged += OnSelectionChangeForMap; + } + + private void OnDisable() + { + Selection.selectionChanged -= OnSelectionChangeForMap; + } + + void OnSelectionChangeForMap() + { + selectedAssets = new(); + foreach (var obj in Selection.objects) + { + if (obj.GetType() != typeof(GameObject)) + { + continue; + } + + string path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(obj); + if (!ADTUtility.IsAdtAny(path)) + { + continue; + } + + selectedAssets[obj.name] = obj as GameObject; + } + Repaint(); + } + + private void OnGUI() + { + GUILayout.Label("All Assets", EditorStyles.boldLabel); + if (GUILayout.Button("Process")) + { + ProcessAssets(); + } + + GUILayout.Space(10); + + GUILayout.Label("Map", EditorStyles.boldLabel); + + if (selectedAssets == null || selectedAssets.Count == 0) + { + GUILayout.Label("No tiles selected. Select some in the project window."); + } else + { + GUILayout.Label("Selected tiles:"); + + foreach (var asset in selectedAssets.Values) + { + EditorGUILayout.ObjectField(asset, typeof(GameObject), false); + } + + GUILayout.BeginHorizontal(); + + try + { + + if (GUILayout.Button("Setup Terrain")) + { + SetupTerrain(); + } + + if (GUILayout.Button("Place Doodads")) + { + PlaceDoodads(); + } + } catch (System.Exception) { + throw; + } finally + { + GUILayout.EndHorizontal(); + } + } + } + + void ProcessAssets() + { + AssetConversionManager.JobPostprocessAllAssets(); + } + + List GetRootAdtPaths() { + List paths = new(); + HashSet pathsH = new(); + + foreach (var selectedAsset in selectedAssets.Values) + { + string path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(selectedAsset); + if (pathsH.Contains(path)) + { + continue; + } + + pathsH.Add(path); + paths.Add(path); + } + + return paths; + } + + void SetupTerrain() + { + foreach (var path in GetRootAdtPaths()) + { + ADTUtility.PostProcessImport(path); + } + + Debug.Log("Done setting up terrain."); + } + + void PlaceDoodads() + { + SetupTerrain(); + + if (AssetConversionManager.HasQueue()) + { + AssetConversionManager.JobPostprocessAllAssets(); + } + + foreach (var path in GetRootAdtPaths()) + { + GameObject prefab = M2Utility.FindPrefab(Path.ChangeExtension(path, "prefab")); + + TextAsset placementData = AssetDatabase.LoadAssetAtPath(Path.ChangeExtension(path, "obj").Replace(".obj", "_ModelPlacementInformation.csv")); + if (placementData == null) + { + Debug.LogWarning($"{path}: ModelPlacementInformation.csv not found."); + continue; + } + + Debug.Log($"{path}: placing doodads..."); + ItemCollectionUtility.PlaceModels(prefab, placementData); + } + + Debug.Log("Done placing doodads."); + } +} diff --git a/Editor/WoWUnityWindow.cs.meta b/Editor/WoWUnityWindow.cs.meta new file mode 100644 index 0000000..09ce765 --- /dev/null +++ b/Editor/WoWUnityWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7292a7181b93ea7449692ee99958c695 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 9b8053724af4e99d5e5b5f3c8906301a9254f0e1 Mon Sep 17 00:00:00 2001 From: David Rios Date: Sun, 7 Jul 2024 23:26:01 -0300 Subject: [PATCH 02/12] Separate M2 from WMO for better performance --- Editor/AssetConversionManager.cs | 98 ++++++++++++++++++++++++++------ Editor/MaterialUtility.cs | 3 +- Editor/WMOUtility.cs | 6 ++ 3 files changed, 88 insertions(+), 19 deletions(-) diff --git a/Editor/AssetConversionManager.cs b/Editor/AssetConversionManager.cs index 65078cc..07fb4d2 100644 --- a/Editor/AssetConversionManager.cs +++ b/Editor/AssetConversionManager.cs @@ -9,7 +9,9 @@ namespace WowUnity { class AssetConversionManager { - private static ConcurrentQueue importedModelPathQueue = new(); + private static readonly ConcurrentQueue importedModelPathQueue = new(); + private static readonly ConcurrentQueue importedWMOPathQueue = new(); + private static readonly ConcurrentQueue physicsQueue = new(); private static bool isBusy = false; public static void QueuePostprocess(string filePath) @@ -19,7 +21,7 @@ public static void QueuePostprocess(string filePath) public static bool HasQueue() { - return importedModelPathQueue.Count > 0; + return importedModelPathQueue.Count + importedWMOPathQueue.Count + physicsQueue.Count > 0; } public static bool IsBusy() @@ -27,6 +29,19 @@ public static bool IsBusy() return isBusy; } + public static string ReadAssetJson(string path) + { + var dirName = Path.GetDirectoryName(path); + string pathToMetadata = dirName + "/" + Path.GetFileNameWithoutExtension(path) + ".json"; + string mainDataPath = Application.dataPath.Replace("Assets", ""); + + var sr = new StreamReader(mainDataPath + pathToMetadata); + var jsonData = sr.ReadToEnd(); + sr.Close(); + + return jsonData; + } + public static void RunPostProcessImports() { var itemsToProcess = importedModelPathQueue.Count; @@ -34,26 +49,51 @@ public static void RunPostProcessImports() List<(string, TextAsset)> hasPlacement = new(); - while (importedModelPathQueue.TryDequeue(out string path)) + AssetDatabase.StartAssetEditing(); + try + { + while (importedModelPathQueue.TryDequeue(out string path)) + { + Debug.Log($"{path}: postprocessing"); + + var jsonData = ReadAssetJson(path); + + if (WMOUtility.IsWMO(jsonData)) + { + // process this separately because of StartAssetEditing issues + importedWMOPathQueue.Enqueue(path); + continue; + } + + if (EditorUtility.DisplayCancelableProgressBar("Postprocessing WoW assets", path, itemsProcessed / itemsToProcess)) + { + break; + } + + M2Utility.PostProcessImport(path, jsonData); + + // process this separately because of StartAssetEditing issues + physicsQueue.Enqueue(path); + + itemsProcessed++; + } + } + finally + { + AssetDatabase.StopAssetEditing(); + AssetDatabase.SaveAssets(); + } + + while (importedWMOPathQueue.TryDequeue(out string path)) { Debug.Log($"{path}: postprocessing"); - if (EditorUtility.DisplayCancelableProgressBar("Postprocessing WoW assets", path, itemsProcessed / itemsToProcess)) + if (EditorUtility.DisplayCancelableProgressBar("Postprocessing WoW WMOs", path, itemsProcessed / itemsToProcess)) { break; } - var dirName = Path.GetDirectoryName(path); - string pathToMetadata = dirName + "/" + Path.GetFileNameWithoutExtension(path) + ".json"; - string mainDataPath = Application.dataPath.Replace("Assets", ""); - - var sr = new StreamReader(mainDataPath + pathToMetadata); - var jsonData = sr.ReadToEnd(); - sr.Close(); - - M2Utility.PostProcessImport(path, jsonData); - WMOUtility.PostProcessImport(path, jsonData); - + WMOUtility.PostProcessImport(path, ReadAssetJson(path)); SetupPhysics(path); TextAsset placementData = AssetDatabase.LoadAssetAtPath(path.Replace(".obj", "_ModelPlacementInformation.csv")); @@ -65,6 +105,23 @@ public static void RunPostProcessImports() itemsProcessed++; } + itemsToProcess = physicsQueue.Count; + itemsProcessed = 0f; + + while (physicsQueue.TryDequeue(out string path)) + { + Debug.Log($"{path}: setup physics"); + + if (EditorUtility.DisplayCancelableProgressBar("Setting up physics", path, itemsProcessed / itemsToProcess)) + { + break; + } + + SetupPhysics(path); + + itemsProcessed++; + } + itemsToProcess = hasPlacement.Count; itemsProcessed = 0f; @@ -117,7 +174,7 @@ public static void SetupPhysics(string path) public static void PostProcessImports() { - if (importedModelPathQueue.Count == 0) + if (importedModelPathQueue.Count + importedWMOPathQueue.Count + physicsQueue.Count == 0) { return; } @@ -127,7 +184,8 @@ public static void PostProcessImports() try { RunPostProcessImports(); - } finally + } + finally { EditorUtility.ClearProgressBar(); } @@ -139,6 +197,9 @@ public static void PostProcessImports() public static void JobPostprocessAllAssets() { importedModelPathQueue.Clear(); + importedWMOPathQueue.Clear(); + physicsQueue.Clear(); + EditorUtility.DisplayProgressBar("Postprocessing WoW assets", "Looking for assets.", 0); try { @@ -150,7 +211,8 @@ public static void JobPostprocessAllAssets() QueuePostprocess(path); } } - } finally + } + finally { EditorUtility.ClearProgressBar(); } diff --git a/Editor/MaterialUtility.cs b/Editor/MaterialUtility.cs index 1c537e3..739d357 100644 --- a/Editor/MaterialUtility.cs +++ b/Editor/MaterialUtility.cs @@ -112,7 +112,6 @@ public static Material GetMaterial(M2Utility.Texture texture, MaterialFor materi } AssetDatabase.CreateAsset(material, assetMatPath); - AssetDatabase.SaveAssets(); return material; } @@ -138,6 +137,7 @@ public static Material GetMaterial(M2Utility.Texture texture, MaterialFor materi (unitMat.flags & (short)MaterialFlags.TwoSided) != (short)MaterialFlags.None); // is two-sided } + AssetDatabase.SaveAssets(); return mats; } @@ -158,6 +158,7 @@ public static Dictionary GetWMOMaterials(WMOUtility.WMO metada } } + AssetDatabase.SaveAssets(); return mats; } diff --git a/Editor/WMOUtility.cs b/Editor/WMOUtility.cs index d1bcf42..ac0d104 100644 --- a/Editor/WMOUtility.cs +++ b/Editor/WMOUtility.cs @@ -8,6 +8,12 @@ namespace WowUnity { class WMOUtility { + public static bool IsWMO(string jsonData) + { + var metadata = JsonConvert.DeserializeObject(jsonData); + return metadata.fileType == "wmo"; + } + public static void PostProcessImport(string path, string jsonData) { var metadata = JsonConvert.DeserializeObject(jsonData); From cc69fedf44b72cbca93f6199266567222da7c77a Mon Sep 17 00:00:00 2001 From: David Rios Date: Mon, 8 Jul 2024 00:40:51 -0300 Subject: [PATCH 03/12] Better handle multiple selected map tiles --- Editor/ADTUtility.cs | 132 +++++++++++++++++++++++++-------------- Editor/WoWUnityWindow.cs | 6 +- 2 files changed, 87 insertions(+), 51 deletions(-) diff --git a/Editor/ADTUtility.cs b/Editor/ADTUtility.cs index 64355d3..faa1d6c 100644 --- a/Editor/ADTUtility.cs +++ b/Editor/ADTUtility.cs @@ -20,58 +20,74 @@ public static bool IsAdtAny(string path) return Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+\.(prefab|obj)$"); } - public static void PostProcessImport(string path) + public static void PostProcessImports(List paths) { - Debug.Log($"{path}: processing adt"); + var total = 0f; + var itemsProcessed = 0f; - if (M2Utility.FindPrefab(path) != null) - { - return; - } - - var imported = AssetDatabase.LoadAssetAtPath(path); - - var dirName = Path.GetDirectoryName(path); - string mainDataPath = Application.dataPath.Replace("Assets", ""); - - Renderer[] renderers = imported.GetComponentsInChildren(); AssetDatabase.StartAssetEditing(); try { - var total = (float)renderers.Count(); - - for (var i = 0; i < total; i++) { - var renderer = renderers[i]; - string pathToMetadata = $"{dirName}/tex_{renderer.name}.json"; - - EditorUtility.DisplayProgressBar("Creating terrain materials.", pathToMetadata, i / total); - - var sr = new StreamReader(mainDataPath + pathToMetadata); - var jsonData = sr.ReadToEnd(); - sr.Close(); - - var metadata = JsonConvert.DeserializeObject(jsonData); - if (metadata.layers.Count == 0) + foreach (var path in paths) + { + if (M2Utility.FindPrefab(path) != null) { continue; } - for (var idx = 0; idx < metadata.layers.Count; idx++) + var imported = AssetDatabase.LoadAssetAtPath(path); + Renderer[] renderers = imported.GetComponentsInChildren(); + total += renderers.Count() + 1; + } + + foreach (var path in paths) + { + if (M2Utility.FindPrefab(path) != null) { - var texture = metadata.layers[idx]; - texture.assetPath = Path.GetRelativePath(mainDataPath, Path.GetFullPath(Path.Join(dirName, texture.file))); - metadata.layers[idx] = texture; + continue; } - renderer.material = MaterialUtility.GetTerrainMaterial(dirName, renderer.name, metadata); + var imported = AssetDatabase.LoadAssetAtPath(path); - //ADT Liquid Volume Queue - // LiquidUtility.QueueLiquidData($"{dirName}/liquid_{renderer.name}.json"); - } + var dirName = Path.GetDirectoryName(path); + var mainDataPath = Application.dataPath.Replace("Assets", ""); - } catch (System.Exception) + Renderer[] renderers = imported.GetComponentsInChildren(); + + foreach (var renderer in renderers) + { + var pathToMetadata = $"{dirName}/tex_{renderer.name}.json"; + + if (EditorUtility.DisplayCancelableProgressBar("Creating terrain materials.", pathToMetadata, itemsProcessed / total)) + { + return; + } + + var sr = new StreamReader(mainDataPath + pathToMetadata); + var jsonData = sr.ReadToEnd(); + sr.Close(); + + var metadata = JsonConvert.DeserializeObject(jsonData); + if (metadata.layers.Count == 0) + { + continue; + } + + for (var idx = 0; idx < metadata.layers.Count; idx++) + { + var texture = metadata.layers[idx]; + texture.assetPath = Path.GetRelativePath(mainDataPath, Path.GetFullPath(Path.Join(dirName, texture.file))); + metadata.layers[idx] = texture; + } + + renderer.material = MaterialUtility.GetTerrainMaterial(dirName, renderer.name, metadata); + itemsProcessed++; + } + } + } + catch (System.Exception) { - Debug.LogError($"{path}: failed processing terrain"); + Debug.LogError($"failed processing terrain materials"); throw; } finally @@ -81,17 +97,41 @@ public static void PostProcessImport(string path) EditorUtility.ClearProgressBar(); } - GameObject prefab = M2Utility.FindOrCreatePrefab(path); + try + { + foreach (var path in paths) + { + if (M2Utility.FindPrefab(path) != null) + { + continue; + } - var rootDoodadSetsObj = new GameObject("EnvironmentSet") { isStatic = true }; + if (EditorUtility.DisplayCancelableProgressBar("Creating terrain materials.", path, itemsProcessed / total)) + { + return; + } - GameObject prefabInst = (GameObject)PrefabUtility.InstantiatePrefab(prefab); - rootDoodadSetsObj.transform.parent = prefabInst.transform; - PrefabUtility.ApplyPrefabInstance(prefabInst, InteractionMode.AutomatedAction); - PrefabUtility.SavePrefabAsset(prefab); - Object.DestroyImmediate(rootDoodadSetsObj); - Object.DestroyImmediate(prefabInst); - AssetDatabase.Refresh(); + //ADT Liquid Volume Queue + // LiquidUtility.QueueLiquidData(xxx); + + GameObject prefab = M2Utility.FindOrCreatePrefab(path); + + var rootDoodadSetsObj = new GameObject("EnvironmentSet") { isStatic = true }; + + GameObject prefabInst = (GameObject)PrefabUtility.InstantiatePrefab(prefab); + rootDoodadSetsObj.transform.parent = prefabInst.transform; + PrefabUtility.ApplyPrefabInstance(prefabInst, InteractionMode.AutomatedAction); + PrefabUtility.SavePrefabAsset(prefab); + Object.DestroyImmediate(rootDoodadSetsObj); + Object.DestroyImmediate(prefabInst); + AssetDatabase.Refresh(); + + itemsProcessed++; + } + } finally + { + EditorUtility.ClearProgressBar(); + } } public class Tex diff --git a/Editor/WoWUnityWindow.cs b/Editor/WoWUnityWindow.cs index 807d61c..7af2ce5 100644 --- a/Editor/WoWUnityWindow.cs +++ b/Editor/WoWUnityWindow.cs @@ -118,11 +118,7 @@ List GetRootAdtPaths() { void SetupTerrain() { - foreach (var path in GetRootAdtPaths()) - { - ADTUtility.PostProcessImport(path); - } - + ADTUtility.PostProcessImports(GetRootAdtPaths()); Debug.Log("Done setting up terrain."); } From 3232661efdd5607483a42a45de074aedaf749e00 Mon Sep 17 00:00:00 2001 From: David Rios Date: Mon, 8 Jul 2024 00:52:39 -0300 Subject: [PATCH 04/12] Move shared variable --- Editor/ADTUtility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Editor/ADTUtility.cs b/Editor/ADTUtility.cs index faa1d6c..fa66e06 100644 --- a/Editor/ADTUtility.cs +++ b/Editor/ADTUtility.cs @@ -40,6 +40,7 @@ public static void PostProcessImports(List paths) total += renderers.Count() + 1; } + var mainDataPath = Application.dataPath.Replace("Assets", ""); foreach (var path in paths) { if (M2Utility.FindPrefab(path) != null) @@ -50,7 +51,6 @@ public static void PostProcessImports(List paths) var imported = AssetDatabase.LoadAssetAtPath(path); var dirName = Path.GetDirectoryName(path); - var mainDataPath = Application.dataPath.Replace("Assets", ""); Renderer[] renderers = imported.GetComponentsInChildren(); From 7e16778bf2a2e67bcb117de16ad0cf2826590926 Mon Sep 17 00:00:00 2001 From: David Rios Date: Mon, 8 Jul 2024 00:53:01 -0300 Subject: [PATCH 05/12] Process assets before adding adt doodads --- Editor/WoWUnityWindow.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Editor/WoWUnityWindow.cs b/Editor/WoWUnityWindow.cs index 7af2ce5..b001c23 100644 --- a/Editor/WoWUnityWindow.cs +++ b/Editor/WoWUnityWindow.cs @@ -124,13 +124,9 @@ void SetupTerrain() void PlaceDoodads() { + ProcessAssets(); SetupTerrain(); - if (AssetConversionManager.HasQueue()) - { - AssetConversionManager.JobPostprocessAllAssets(); - } - foreach (var path in GetRootAdtPaths()) { GameObject prefab = M2Utility.FindPrefab(Path.ChangeExtension(path, "prefab")); From bea48185ba699d83d0273e8ae50a947e78920af9 Mon Sep 17 00:00:00 2001 From: David Rios Date: Mon, 8 Jul 2024 00:59:54 -0300 Subject: [PATCH 06/12] Reuse regex --- Editor/WoWExportUnityPostProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Editor/WoWExportUnityPostProcessor.cs b/Editor/WoWExportUnityPostProcessor.cs index 0ad7c8e..790e310 100644 --- a/Editor/WoWExportUnityPostProcessor.cs +++ b/Editor/WoWExportUnityPostProcessor.cs @@ -25,7 +25,7 @@ static public bool ValidAsset(string path) return ( File.Exists(Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + ".json") || - Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+.obj$") + ADTUtility.IsAdtObj(path) ); } From 8ff69e8dc1e706c5920751ed2180f6b73300bdf4 Mon Sep 17 00:00:00 2001 From: David Rios Date: Mon, 8 Jul 2024 09:46:17 -0300 Subject: [PATCH 07/12] Cancel should return, not just break --- Editor/AssetConversionManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Editor/AssetConversionManager.cs b/Editor/AssetConversionManager.cs index 07fb4d2..d3f0288 100644 --- a/Editor/AssetConversionManager.cs +++ b/Editor/AssetConversionManager.cs @@ -67,7 +67,7 @@ public static void RunPostProcessImports() if (EditorUtility.DisplayCancelableProgressBar("Postprocessing WoW assets", path, itemsProcessed / itemsToProcess)) { - break; + return; } M2Utility.PostProcessImport(path, jsonData); @@ -90,7 +90,7 @@ public static void RunPostProcessImports() if (EditorUtility.DisplayCancelableProgressBar("Postprocessing WoW WMOs", path, itemsProcessed / itemsToProcess)) { - break; + return; } WMOUtility.PostProcessImport(path, ReadAssetJson(path)); @@ -114,7 +114,7 @@ public static void RunPostProcessImports() if (EditorUtility.DisplayCancelableProgressBar("Setting up physics", path, itemsProcessed / itemsToProcess)) { - break; + return; } SetupPhysics(path); @@ -129,7 +129,7 @@ public static void RunPostProcessImports() { if (EditorUtility.DisplayCancelableProgressBar("Placing doodads", path, itemsProcessed / itemsToProcess)) { - break; + return; } Debug.Log($"{path}: placing models"); ItemCollectionUtility.PlaceModels(M2Utility.FindPrefab(path), placementData); From e9ec348df2ceab5763b4b24fe41fe8643f070067 Mon Sep 17 00:00:00 2001 From: David Rios Date: Mon, 8 Jul 2024 13:36:49 -0300 Subject: [PATCH 08/12] Set correct smoothness --- Editor/MaterialUtility.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Editor/MaterialUtility.cs b/Editor/MaterialUtility.cs index 739d357..77154f5 100644 --- a/Editor/MaterialUtility.cs +++ b/Editor/MaterialUtility.cs @@ -55,6 +55,7 @@ public static Material GetMaterial(M2Utility.Texture texture, MaterialFor materi material.SetFloat("_WorkflowMode", 0); material.SetFloat("_Smoothness", 0); if (shader == 1) { + material.SetFloat("_Smoothness", 1); material.SetFloat("_SmoothnessTextureChannel", 1); } From 4ea54888aab2f506f55bc1f7d7460a6adb37536d Mon Sep 17 00:00:00 2001 From: David Rios Date: Fri, 12 Jul 2024 00:09:54 -0300 Subject: [PATCH 09/12] Fix inverted condition --- Editor/WMOUtility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Editor/WMOUtility.cs b/Editor/WMOUtility.cs index ac0d104..5c30d25 100644 --- a/Editor/WMOUtility.cs +++ b/Editor/WMOUtility.cs @@ -50,7 +50,7 @@ public static void PostProcessImport(string path, string jsonData) foreach (var doodadSet in metadata.doodadSets) { var setObj = new GameObject(doodadSet.name) { isStatic = true }; setObj.transform.parent = rootDoodadSetsObj.transform; - setObj.SetActive(doodadSet.name != "Set_$DefaultGlobal"); + setObj.SetActive(doodadSet.name == "Set_$DefaultGlobal"); } GameObject prefabInst = (GameObject)PrefabUtility.InstantiatePrefab(prefab); From 7f59a05ce8262f2aaacf5bdbe9e9d5d623ef6b04 Mon Sep 17 00:00:00 2001 From: David Rios Date: Sat, 13 Jul 2024 20:15:02 -0300 Subject: [PATCH 10/12] Only create doodad root when placing them --- Editor/ADTUtility.cs | 30 ++---------- Editor/ItemCollectionUtility.cs | 85 ++++++++++++++++++++++----------- Editor/WMOUtility.cs | 19 +------- 3 files changed, 61 insertions(+), 73 deletions(-) diff --git a/Editor/ADTUtility.cs b/Editor/ADTUtility.cs index fa66e06..78dabb2 100644 --- a/Editor/ADTUtility.cs +++ b/Editor/ADTUtility.cs @@ -12,12 +12,12 @@ class ADTUtility { public static bool IsAdtObj(string path) { - return Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+\.obj$"); + return Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+(_.+)?\.obj$"); } public static bool IsAdtAny(string path) { - return Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+\.(prefab|obj)$"); + return Regex.IsMatch(Path.GetFileName(path), @"^adt_\d+_\d+(_.+)?\.(prefab|obj)$"); } public static void PostProcessImports(List paths) @@ -101,31 +101,7 @@ public static void PostProcessImports(List paths) { foreach (var path in paths) { - if (M2Utility.FindPrefab(path) != null) - { - continue; - } - - if (EditorUtility.DisplayCancelableProgressBar("Creating terrain materials.", path, itemsProcessed / total)) - { - return; - } - - //ADT Liquid Volume Queue - // LiquidUtility.QueueLiquidData(xxx); - - GameObject prefab = M2Utility.FindOrCreatePrefab(path); - - var rootDoodadSetsObj = new GameObject("EnvironmentSet") { isStatic = true }; - - GameObject prefabInst = (GameObject)PrefabUtility.InstantiatePrefab(prefab); - rootDoodadSetsObj.transform.parent = prefabInst.transform; - PrefabUtility.ApplyPrefabInstance(prefabInst, InteractionMode.AutomatedAction); - PrefabUtility.SavePrefabAsset(prefab); - Object.DestroyImmediate(rootDoodadSetsObj); - Object.DestroyImmediate(prefabInst); - AssetDatabase.Refresh(); - + M2Utility.FindOrCreatePrefab(path); itemsProcessed++; } } finally diff --git a/Editor/ItemCollectionUtility.cs b/Editor/ItemCollectionUtility.cs index 7ed114e..396c5aa 100644 --- a/Editor/ItemCollectionUtility.cs +++ b/Editor/ItemCollectionUtility.cs @@ -25,7 +25,7 @@ public static void PlaceModels(GameObject prefab, TextAsset modelPlacementInform childTransform.gameObject.isStatic = true; } - ParseFileAndSpawnDoodads(instantiatedGameObject, modelPlacementInformation); + PlaceModelsOnPrefab(instantiatedGameObject, modelPlacementInformation); PrefabUtility.ApplyPrefabInstance(instantiatedGameObject, InteractionMode.AutomatedAction); PrefabUtility.SavePrefabAsset(prefab); @@ -33,16 +33,23 @@ public static void PlaceModels(GameObject prefab, TextAsset modelPlacementInform Object.DestroyImmediate(instantiatedGameObject); } - private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, TextAsset modelPlacementInformation) + private static Transform GetOrCreateRoot(Transform destination, string name) + { + var root = destination.Find(name); + if (root == null) + { + var newRootGO = new GameObject(name) { isStatic = true }; + newRootGO.transform.parent = destination; + root = newRootGO.transform; + } + return root; + } + + private static void PlaceModelsOnPrefab(GameObject instantiatedPrefabGObj, TextAsset modelPlacementInformation) { var isAdt = Regex.IsMatch(modelPlacementInformation.name, @"adt_\d+_\d+"); - Transform doodadSetRoot; - if (isAdt) { - doodadSetRoot = instantiatedPrefabGObj.transform.Find("EnvironmentSet"); - } else { - doodadSetRoot = instantiatedPrefabGObj.transform.Find("DoodadSets"); - } + var doodadSetRoot = GetOrCreateRoot(instantiatedPrefabGObj.transform, isAdt ? "EnvironmentSet" : "DoodadSets"); if (doodadSetRoot == null) { @@ -51,20 +58,29 @@ private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, } if (doodadSetRoot.Find("doodadsplaced") != null) - { return; - } + ParseFileAndSpawnDoodads(doodadSetRoot, modelPlacementInformation); + if (isAdt) + ParseFileAndSpawnDoodads(GetOrCreateRoot(instantiatedPrefabGObj.transform, "WMOSet"), modelPlacementInformation, typeToPlace: "wmo"); + + var placed = new GameObject("doodadsplaced"); + placed.transform.parent = doodadSetRoot.transform; + } + + private static void ParseFileAndSpawnDoodads(Transform doodadSetRoot, TextAsset modelPlacementInformation, string typeToPlace = "m2", bool useSetSubtrees = true) + { + var isAdt = Regex.IsMatch(modelPlacementInformation.name, @"adt_\d+_\d+"); + + var placementFileDir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(modelPlacementInformation)); string[] records = modelPlacementInformation.text.Split(CSV_LINE_SEPERATOR); foreach (string record in records.Skip(1)) { string[] fields = record.Split(CSV_COLUMN_SEPERATOR); if (fields.Length < 11) - { continue; - } - string doodadPath = Path.GetDirectoryName(PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(instantiatedPrefabGObj)) + Path.DirectorySeparatorChar + fields[0]; + string doodadPath = placementFileDir + Path.DirectorySeparatorChar + fields[0]; doodadPath = Path.GetFullPath(doodadPath); doodadPath = $"Assets{Path.DirectorySeparatorChar}" + doodadPath.Substring(Application.dataPath.Length + 1); //This is so nifty :3 @@ -74,6 +90,10 @@ private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, if (isAdt) { + var doodadType = fields[10]; + if (doodadType != typeToPlace) + continue; + doodadPosition.x = MAXIMUM_DISTANCE_FROM_ORIGIN - float.Parse(fields[1], CultureInfo.InvariantCulture); doodadPosition.z = (MAXIMUM_DISTANCE_FROM_ORIGIN - float.Parse(fields[3], CultureInfo.InvariantCulture)) * -1f; doodadPosition.y = float.Parse(fields[2], CultureInfo.InvariantCulture); @@ -86,35 +106,44 @@ private static void ParseFileAndSpawnDoodads(GameObject instantiatedPrefabGObj, doodadRotation.eulerAngles = eulerRotation; var spawned = SpawnDoodad(doodadPath, doodadPosition, doodadRotation, doodadScale, doodadSetRoot); - var doodadSets = fields[13].Split(","); - foreach (var setName in doodadSets) { - var childObj = spawned.transform.Find($"DoodadSets/{setName}"); - if (childObj != null) { - childObj.gameObject.SetActive(true); + if (doodadType == "wmo") + { + var doodadSets = fields[13].Split(","); + foreach (var setName in doodadSets) + { + var childObj = spawned.transform.Find($"DoodadSets/{setName}"); + if (childObj != null) + { + childObj.gameObject.SetActive(true); + } } } } else { doodadPosition = new Vector3( - float.Parse(fields[1], CultureInfo.InvariantCulture), - float.Parse(fields[3], CultureInfo.InvariantCulture), + float.Parse(fields[1], CultureInfo.InvariantCulture), + float.Parse(fields[3], CultureInfo.InvariantCulture), float.Parse(fields[2], CultureInfo.InvariantCulture) ); doodadRotation = new Quaternion( - float.Parse(fields[5], CultureInfo.InvariantCulture) * -1, - float.Parse(fields[7], CultureInfo.InvariantCulture), - float.Parse(fields[6], CultureInfo.InvariantCulture) * -1, + float.Parse(fields[5], CultureInfo.InvariantCulture) * -1, + float.Parse(fields[7], CultureInfo.InvariantCulture), + float.Parse(fields[6], CultureInfo.InvariantCulture) * -1, float.Parse(fields[4], CultureInfo.InvariantCulture) * -1 ); - var doodadSubsetRoot = doodadSetRoot.transform.Find(fields[9]); - SpawnDoodad(doodadPath, doodadPosition, doodadRotation, doodadScale, doodadSubsetRoot); + var setName = fields[9]; + var placementRoot = doodadSetRoot; + if (useSetSubtrees) + { + placementRoot = GetOrCreateRoot(doodadSetRoot, setName); + placementRoot.gameObject.SetActive(setName == "Set_$DefaultGlobal"); + } + + SpawnDoodad(doodadPath, doodadPosition, doodadRotation, doodadScale, placementRoot); } } - - var placed = new GameObject("doodadsplaced"); - placed.transform.parent = doodadSetRoot.transform; } private static GameObject SpawnDoodad(string path, Vector3 position, Quaternion rotation, float scaleFactor, Transform parent) diff --git a/Editor/WMOUtility.cs b/Editor/WMOUtility.cs index 5c30d25..75c3beb 100644 --- a/Editor/WMOUtility.cs +++ b/Editor/WMOUtility.cs @@ -43,24 +43,7 @@ public static void PostProcessImport(string path, string jsonData) } AssetDatabase.Refresh(); - GameObject prefab = M2Utility.FindOrCreatePrefab(path); - - if (metadata.doodadSets.Count > 0) { - var rootDoodadSetsObj = new GameObject("DoodadSets") { isStatic = true }; - foreach (var doodadSet in metadata.doodadSets) { - var setObj = new GameObject(doodadSet.name) { isStatic = true }; - setObj.transform.parent = rootDoodadSetsObj.transform; - setObj.SetActive(doodadSet.name == "Set_$DefaultGlobal"); - } - - GameObject prefabInst = (GameObject)PrefabUtility.InstantiatePrefab(prefab); - rootDoodadSetsObj.transform.parent = prefabInst.transform; - PrefabUtility.ApplyPrefabInstance(prefabInst, InteractionMode.AutomatedAction); - PrefabUtility.SavePrefabAsset(prefab); - Object.DestroyImmediate(rootDoodadSetsObj); - Object.DestroyImmediate(prefabInst); - AssetDatabase.Refresh(); - } + M2Utility.FindOrCreatePrefab(path); } public static bool AssignVertexColors(WMOUtility.Group group, List gameObjects) From 94e9f71954a459fef8e831a0e5ca0f61eabe100e Mon Sep 17 00:00:00 2001 From: David Rios Date: Sun, 25 Aug 2024 10:37:53 -0300 Subject: [PATCH 11/12] Change visibility --- Editor/ADTUtility.cs | 2 +- Editor/AssetConversionManager.cs | 2 +- Editor/M2Utility.cs | 2 +- Editor/MaterialUtility.cs | 2 +- Editor/WMOUtility.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Editor/ADTUtility.cs b/Editor/ADTUtility.cs index 78dabb2..cf21495 100644 --- a/Editor/ADTUtility.cs +++ b/Editor/ADTUtility.cs @@ -8,7 +8,7 @@ namespace WowUnity { - class ADTUtility + public class ADTUtility { public static bool IsAdtObj(string path) { diff --git a/Editor/AssetConversionManager.cs b/Editor/AssetConversionManager.cs index d3f0288..bba4227 100644 --- a/Editor/AssetConversionManager.cs +++ b/Editor/AssetConversionManager.cs @@ -7,7 +7,7 @@ namespace WowUnity { - class AssetConversionManager + public class AssetConversionManager { private static readonly ConcurrentQueue importedModelPathQueue = new(); private static readonly ConcurrentQueue importedWMOPathQueue = new(); diff --git a/Editor/M2Utility.cs b/Editor/M2Utility.cs index 4934423..934415b 100644 --- a/Editor/M2Utility.cs +++ b/Editor/M2Utility.cs @@ -7,7 +7,7 @@ namespace WowUnity { - class M2Utility + public class M2Utility { public static void PostProcessImport(string path, string jsonData) { diff --git a/Editor/MaterialUtility.cs b/Editor/MaterialUtility.cs index 77154f5..9b8f9c3 100644 --- a/Editor/MaterialUtility.cs +++ b/Editor/MaterialUtility.cs @@ -6,7 +6,7 @@ namespace WowUnity { - class MaterialUtility + public class MaterialUtility { public const string LIT_SHADER = "Universal Render Pipeline/Lit"; public const string UNLIT_SHADER = "Universal Render Pipeline/Unlit"; diff --git a/Editor/WMOUtility.cs b/Editor/WMOUtility.cs index 75c3beb..ae2e18a 100644 --- a/Editor/WMOUtility.cs +++ b/Editor/WMOUtility.cs @@ -6,7 +6,7 @@ namespace WowUnity { - class WMOUtility + public class WMOUtility { public static bool IsWMO(string jsonData) { From 0f115c0f65a1dd0737e7bfe96b23afdd84c915ab Mon Sep 17 00:00:00 2001 From: David Rios Date: Sun, 25 Aug 2024 10:59:02 -0300 Subject: [PATCH 12/12] Fix materials not persisting after restart --- Editor/ADTUtility.cs | 11 +++++--- Editor/ItemCollectionUtility.cs | 2 +- Editor/M2Utility.cs | 47 +++++++++++++-------------------- Editor/WMOUtility.cs | 6 ++--- 4 files changed, 29 insertions(+), 37 deletions(-) diff --git a/Editor/ADTUtility.cs b/Editor/ADTUtility.cs index cf21495..75c0b49 100644 --- a/Editor/ADTUtility.cs +++ b/Editor/ADTUtility.cs @@ -25,6 +25,7 @@ public static void PostProcessImports(List paths) var total = 0f; var itemsProcessed = 0f; + List<(string, GameObject)> instances = new(); AssetDatabase.StartAssetEditing(); try { @@ -48,11 +49,11 @@ public static void PostProcessImports(List paths) continue; } - var imported = AssetDatabase.LoadAssetAtPath(path); + var importedInstance = M2Utility.InstantiateImported(path); var dirName = Path.GetDirectoryName(path); - Renderer[] renderers = imported.GetComponentsInChildren(); + Renderer[] renderers = importedInstance.GetComponentsInChildren(); foreach (var renderer in renderers) { @@ -83,6 +84,8 @@ public static void PostProcessImports(List paths) renderer.material = MaterialUtility.GetTerrainMaterial(dirName, renderer.name, metadata); itemsProcessed++; } + + instances.Add((path, importedInstance)); } } catch (System.Exception) @@ -99,9 +102,9 @@ public static void PostProcessImports(List paths) try { - foreach (var path in paths) + foreach (var (path, instance) in instances) { - M2Utility.FindOrCreatePrefab(path); + M2Utility.SaveAsPrefab(instance, path); itemsProcessed++; } } finally diff --git a/Editor/ItemCollectionUtility.cs b/Editor/ItemCollectionUtility.cs index 396c5aa..f717349 100644 --- a/Editor/ItemCollectionUtility.cs +++ b/Editor/ItemCollectionUtility.cs @@ -148,7 +148,7 @@ private static void ParseFileAndSpawnDoodads(Transform doodadSetRoot, TextAsset private static GameObject SpawnDoodad(string path, Vector3 position, Quaternion rotation, float scaleFactor, Transform parent) { - GameObject exisitingPrefab = M2Utility.FindOrCreatePrefab(path); + GameObject exisitingPrefab = M2Utility.FindPrefab(path); if (exisitingPrefab == null) { diff --git a/Editor/M2Utility.cs b/Editor/M2Utility.cs index 934415b..21852eb 100644 --- a/Editor/M2Utility.cs +++ b/Editor/M2Utility.cs @@ -25,9 +25,9 @@ public static void PostProcessImport(string path, string jsonData) ProcessTextures(metadata.textures, Path.GetDirectoryName(path)); - var imported = AssetDatabase.LoadAssetAtPath(path); + var importedInstance = InstantiateImported(path); - Renderer[] renderers = imported.GetComponentsInChildren(); + Renderer[] renderers = importedInstance.GetComponentsInChildren(); var skinMaterials = MaterialUtility.GetSkinMaterials(metadata); @@ -40,7 +40,7 @@ public static void PostProcessImport(string path, string jsonData) } AssetDatabase.Refresh(); - GeneratePrefab(path); + SaveAsPrefab(importedInstance, path); if (metadata.textureTransforms.Count > 0 && metadata.textureTransforms[0].translation.timestamps.Count > 0) { @@ -52,28 +52,9 @@ public static void PostProcessImport(string path, string jsonData) } } - public static GameObject FindOrCreatePrefab(string path) + public static GameObject InstantiateImported(string path) { - GameObject existingPrefab = FindPrefab(path); - - if (existingPrefab == null) - { - return GeneratePrefab(path); - } - - return existingPrefab; - } - - public static GameObject FindPrefab(string path) - { - string prefabPath = Path.ChangeExtension(path, "prefab"); - return AssetDatabase.LoadAssetAtPath(prefabPath); - } - - public static GameObject GeneratePrefab(string path) - { - string prefabPath = Path.ChangeExtension(path, "prefab"); - GameObject importedModelObject = AssetDatabase.LoadAssetAtPath(path); + var importedModelObject = AssetDatabase.LoadAssetAtPath(path); if (importedModelObject == null) { @@ -82,7 +63,7 @@ public static GameObject GeneratePrefab(string path) } var rootObj = new GameObject() { isStatic = true }; - GameObject rootModelInstance = PrefabUtility.InstantiatePrefab(importedModelObject, rootObj.transform) as GameObject; + var rootModelInstance = PrefabUtility.InstantiatePrefab(importedModelObject, rootObj.transform) as GameObject; //Set the object as static, and all it's child objects rootModelInstance.isStatic = true; @@ -91,11 +72,19 @@ public static GameObject GeneratePrefab(string path) childTransform.gameObject.isStatic = true; } - GameObject newPrefab = PrefabUtility.SaveAsPrefabAssetAndConnect(rootObj, prefabPath, InteractionMode.AutomatedAction); - AssetDatabase.Refresh(); - UnityEngine.Object.DestroyImmediate(rootObj); + return rootObj; + } - return newPrefab; + public static void SaveAsPrefab(GameObject gameObject, string path) + { + PrefabUtility.SaveAsPrefabAsset(gameObject, Path.ChangeExtension(path, ".prefab")); + UnityEngine.Object.DestroyImmediate(gameObject); + } + + public static GameObject FindPrefab(string path) + { + string prefabPath = Path.ChangeExtension(path, "prefab"); + return AssetDatabase.LoadAssetAtPath(prefabPath); } public static void ProcessTextures(List textures, string dirName) { diff --git a/Editor/WMOUtility.cs b/Editor/WMOUtility.cs index ae2e18a..cf144c3 100644 --- a/Editor/WMOUtility.cs +++ b/Editor/WMOUtility.cs @@ -30,9 +30,9 @@ public static void PostProcessImport(string path, string jsonData) M2Utility.ProcessTextures(metadata.textures, Path.GetDirectoryName(path)); - var imported = AssetDatabase.LoadAssetAtPath(path); + var importedInstance = M2Utility.InstantiateImported(path); - Renderer[] renderers = imported.GetComponentsInChildren(); + Renderer[] renderers = importedInstance.GetComponentsInChildren(); var materials = MaterialUtility.GetWMOMaterials(metadata); @@ -43,7 +43,7 @@ public static void PostProcessImport(string path, string jsonData) } AssetDatabase.Refresh(); - M2Utility.FindOrCreatePrefab(path); + M2Utility.SaveAsPrefab(importedInstance, path); } public static bool AssignVertexColors(WMOUtility.Group group, List gameObjects)